Extend SDK layer 1 docs (#5011)

main
Maxim Zhiltsov 3 years ago committed by GitHub
parent bcb3f9bda8
commit 426f8e3ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -64,12 +64,13 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Create image directory
- name: Create artifact directories
run: |
mkdir /tmp/cvat_server
mkdir /tmp/cvat_ui
mkdir /tmp/cvat_sdk
- name: CVAT server. Build
- name: CVAT server. Build and push
uses: docker/build-push-action@v3
with:
cache-from: type=local,src=/tmp/cvat_cache_server
@ -78,7 +79,7 @@ jobs:
tags: cvat/server
outputs: type=docker,dest=/tmp/cvat_server/image.tar
- name: CVAT UI. Build
- name: CVAT UI. Build and push
uses: docker/build-push-action@v3
with:
cache-from: type=local,src=/tmp/cvat_cache_ui
@ -87,6 +88,18 @@ jobs:
tags: cvat/ui
outputs: type=docker,dest=/tmp/cvat_ui/image.tar
- name: CVAT SDK. Build
run: |
docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \
--entrypoint /bin/bash -u root cvat/server \
-c 'python manage.py spectacular --file /transfer/schema.yml'
pip3 install --user -r cvat-sdk/gen/requirements.txt
cd cvat-sdk/
gen/generate.sh
cd ..
cp -r cvat-sdk/* /tmp/cvat_sdk/
- name: Upload CVAT server artifact
uses: actions/upload-artifact@v3
with:
@ -99,6 +112,12 @@ jobs:
name: cvat_ui
path: /tmp/cvat_ui/image.tar
- name: Upload CVAT SDK artifact
uses: actions/upload-artifact@v3
with:
name: cvat_sdk
path: /tmp/cvat_sdk/
rest_api:
needs: build
runs-on: ubuntu-latest
@ -158,6 +177,12 @@ jobs:
name: cvat_ui
path: /tmp/cvat_ui/
- name: Download CVAT SDK package
uses: actions/download-artifact@v3
with:
name: cvat_sdk
path: /tmp/cvat_sdk/
- name: Load Docker images
run: |
docker load --input /tmp/cvat_server/image.tar
@ -168,15 +193,7 @@ jobs:
- name: Running REST API and SDK tests
run: |
docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \
--entrypoint /bin/bash -u root cvat/server \
-c 'python manage.py spectacular --file /transfer/schema.yml'
pip3 install --user -r cvat-sdk/gen/requirements.txt
cd cvat-sdk/
gen/generate.sh
cd ..
pip3 install --user cvat-sdk/
pip3 install --user /tmp/cvat_sdk/
pip3 install --user cvat-cli/
pip3 install --user -r tests/python/requirements.txt
pytest tests/python -s -v

@ -1,43 +0,0 @@
name: Github pages
on:
push:
branches:
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.83.1'
extended: true
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Install npm packages
working-directory: ./site
run: |
npm ci
- name: Build docs
run: |
pip install -r site/requirements.txt
python site/build_docs.py
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
force_orphan: true

@ -65,10 +65,11 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Create image directory
- name: Create artifact directories
run: |
mkdir /tmp/cvat_server
mkdir /tmp/cvat_ui
mkdir /tmp/cvat_sdk
- name: CVAT server. Build and push
uses: docker/build-push-action@v3
@ -88,6 +89,18 @@ jobs:
tags: cvat/ui
outputs: type=docker,dest=/tmp/cvat_ui/image.tar
- name: CVAT SDK. Build
run: |
docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \
--entrypoint /bin/bash -u root cvat/server \
-c 'python manage.py spectacular --file /transfer/schema.yml'
pip3 install --user -r cvat-sdk/gen/requirements.txt
cd cvat-sdk/
gen/generate.sh
cd ..
cp -r cvat-sdk/* /tmp/cvat_sdk/
- name: Upload CVAT server artifact
uses: actions/upload-artifact@v3
with:
@ -100,7 +113,13 @@ jobs:
name: cvat_ui
path: /tmp/cvat_ui/image.tar
rest_api:
- name: Upload CVAT SDK artifact
uses: actions/upload-artifact@v3
with:
name: cvat_sdk
path: /tmp/cvat_sdk/
rest_api_testing:
needs: build
runs-on: ubuntu-latest
steps:
@ -125,6 +144,12 @@ jobs:
name: cvat_ui
path: /tmp/cvat_ui/
- name: Download CVAT SDK package
uses: actions/download-artifact@v3
with:
name: cvat_sdk
path: /tmp/cvat_sdk/
- name: Load Docker images
run: |
docker load --input /tmp/cvat_server/image.tar
@ -135,22 +160,14 @@ jobs:
- name: Running REST API tests
run: |
docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \
--entrypoint /bin/bash -u root cvat/server \
-c 'python manage.py spectacular --file /transfer/schema.yml'
pip3 install --user -r cvat-sdk/gen/requirements.txt
cd cvat-sdk/
gen/generate.sh
cd ..
pip3 install --user cvat-sdk/
pip3 install --user /tmp/cvat_sdk/
pip3 install --user -r tests/python/requirements.txt
pytest tests/python/rest_api -k 'GET' -s
- name: Creating a log file from cvat containers
if: failure()
env:
LOGS_DIR: "${{ github.workspace }}/rest_api"
LOGS_DIR: "${{ github.workspace }}/rest_api_testing"
run: |
mkdir $LOGS_DIR
docker logs test_cvat_server_1 > $LOGS_DIR/cvat.log
@ -161,7 +178,7 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: container_logs
path: "${{ github.workspace }}/rest_api"
path: "${{ github.workspace }}/rest_api_testing"
unit_testing:
needs: build
@ -320,9 +337,64 @@ jobs:
name: cypress_screenshots_${{ matrix.specs }}
path: ${{ github.workspace }}/tests/cypress/screenshots
generate_github_pages:
if: github.ref == 'refs/heads/develop'
needs: [rest_api_testing, unit_testing, e2e_testing]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- name: Download CVAT server images
uses: actions/download-artifact@v3
with:
name: cvat_server
path: /tmp/cvat_server/
- name: Download CVAT server images
uses: actions/download-artifact@v3
with:
name: cvat_sdk
path: /tmp/cvat_sdk/
- name: Load Docker images
run: |
docker load --input /tmp/cvat_server/image.tar
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.83.1'
extended: true
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Install npm packages
working-directory: ./site
run: |
npm ci
- name: Build docs
run: |
pip install -r site/requirements.txt
python site/process_sdk_docs.py --input-dir /tmp/cvat_sdk/docs/ --site-root site/
python site/build_docs.py
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
force_orphan: true
publish_dev_images:
if: github.ref == 'refs/heads/develop'
needs: [rest_api, unit_testing, e2e_testing]
needs: [rest_api_testing, unit_testing, e2e_testing, generate_github_pages]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

@ -252,6 +252,21 @@
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
},
{
"name": "sdk docs: Postprocess generated docs",
"type": "python",
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"python": "${command:python.interpreterPath}",
"program": "${workspaceFolder}/site/process_sdk_docs.py",
"args": [
"--input-dir", "${workspaceFolder}/cvat-sdk/docs/",
"--site-root", "${workspaceFolder}/site/",
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
},
{
"name": "server: Generate REST API Schema",
"type": "python",

@ -1,60 +0,0 @@
# API design decisions
Generated API is modified from what `openapi-generator` does by default.
Changes are mostly focused on better user experience - including better
usage patterns and simpler/faster ways to achieve results.
## Changes
- Added type annotations for return types and class members
This change required us to implement a custom post-processing script,
which converts generated types into correct type annotations. The types
generated by default are supposed to work with the API implementation
(parameter validation and parsing), but they are not applicable as
type annotations (they have incorrect syntax). Custom post-processing
allowed us to make these types correct type annotations.
Other possible solutions:
- There is a `python-experimental` API generator, which may solve
some issues, but it is unstable and requires python 3.9. Our API
works with 3.7, which is the lowest supported version now.
- Custom templates - partially works, but only in limited cases
(model fields). It's very hard to maintain the template code and
logic for this. Only `if` checks and `for` loops are available in
mustache templates, which is not enough for annotation generation.
- Separate APIs are embedded into the general `APIClient` class
Now we have:
```python
with ApiClient(config) as api_client:
result1 = api_client.foo_api.operation1()
result2 = api_client.bar_api.operation2()
```
This showed to be more convenient than default:
```python
with ApiClient(config) as api_client:
foo_api = FooApi(api_client)
result1 = foo_api.operation1()
result2 = foo_api.operation2()
bar_api = BarApi(api_client)
result3 = bar_api.operation3()
result4 = bar_api.operation4()
```
This also required custom post-processing. Operation Ids are
[supposed to be unique](https://swagger.io/specification/#operation-object)
in the OpenAPI / Swagger specification. Therefore, we can't generate such
schema on the server, nor we can't expect it to be supported in the
API generator.
- Operations have IDs like `<api>/<method>_<object>`
This also showed to be more readable and more natural than DRF-spectacular's
default `<api>/<object>_<method>`.
- Server operations have different types for input and output values
While it can be expected that an endopint with POST/PUT methods available
(like `create` or `partial_update`) has the same type for input and output
(because it looks natural), it also leads to the situation, in which there
are lots of read-/write-only fields, and it becomes hard for understanding.
This clear type separation is supposed to make it simpler for users.

@ -13,7 +13,7 @@ LIB_NAME="cvat_sdk"
LAYER1_LIB_NAME="${LIB_NAME}/api_client"
DST_DIR="."
TEMPLATE_DIR="gen"
PYTHON_POST_PROCESS_FILE="${TEMPLATE_DIR}/postprocess.py"
POST_PROCESS_SCRIPT="${TEMPLATE_DIR}/postprocess.py"
mkdir -p "${DST_DIR}/"
rm -f -r "${DST_DIR}/docs" "${DST_DIR}/${LAYER1_LIB_NAME}" "requirements/"
@ -34,5 +34,19 @@ cp -r "${TEMPLATE_DIR}/templates/requirements" "${DST_DIR}/"
cp -r "${TEMPLATE_DIR}/templates/MANIFEST.in" "${DST_DIR}/"
mv "${DST_DIR}/requirements.txt" "${DST_DIR}/requirements/api_client.txt"
# Do custom postprocessing
"${PYTHON_POST_PROCESS_FILE}" --schema "schema/schema.yml" --input-path "${DST_DIR}/${LIB_NAME}"
# Do custom postprocessing for code files
"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/${LIB_NAME}"
# Do custom postprocessing for docs files
"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/docs" --file-ext '.md'
"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/README.md"
API_DOCS_DIR="${DST_DIR}/docs/apis/"
MODEL_DOCS_DIR="${DST_DIR}/docs/models/"
mkdir "${API_DOCS_DIR}"
mkdir "${MODEL_DOCS_DIR}"
mv "${DST_DIR}/docs/"*Api.md "${API_DOCS_DIR}"
mv "${DST_DIR}/docs/"*.md "${MODEL_DOCS_DIR}"
mv "${DST_DIR}/README.md" "${DST_DIR}/docs/"
cp "${TEMPLATE_DIR}/templates/README.md.template" "${DST_DIR}/README.md"

@ -1,13 +1,13 @@
#!/usr/bin/env python
# Copyright (C) 2022 Intel Corporation
# Copyright (C) 2022 CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
import argparse
import os.path as osp
import re
import sys
from argparse import ArgumentParser
from glob import glob
from inflection import underscore
@ -29,7 +29,7 @@ def collect_operations(schema):
return operations
class Processor:
class Replacer:
REPLACEMENT_TOKEN = r"%%%"
ARGS_TOKEN = r"!!!"
@ -77,7 +77,7 @@ class Processor:
def _process_file(self, contents: str):
processor_pattern = re.compile(
f"{self.REPLACEMENT_TOKEN}(.*?){self.ARGS_TOKEN}(.*){self.REPLACEMENT_TOKEN}"
f"{self.REPLACEMENT_TOKEN}(.*?){self.ARGS_TOKEN}(.*?){self.REPLACEMENT_TOKEN}"
)
matches = list(processor_pattern.finditer(contents))
@ -102,8 +102,8 @@ class Processor:
with open(src_path, "w") as f:
f.write(contents)
def process_dir(self, dir_path: str):
for filename in glob(dir_path + "/**/*.py", recursive=True):
def process_dir(self, dir_path: str, *, file_ext: str = ".py"):
for filename in glob(dir_path + f"/**/*{file_ext}", recursive=True):
try:
self.process_file(filename)
except Exception as e:
@ -117,26 +117,36 @@ def parse_schema(path):
def parse_args(args=None):
parser = ArgumentParser(
parser = argparse.ArgumentParser(
add_help=True,
description="""
Processes generator output files in a custom way, saves results inplace.
Replacement token: '%(repl_token)s'.
Args separator token: '%(args_token)s'.
Replaces the following patterns in python files:
'%(repl_token)sREPLACER%(args_token)sARG1%(args_token)sARG2...%(repl_token)s'
->
REPLACER(ARG1, ARG2, ...) value
formatter_class=argparse.RawTextHelpFormatter,
description="""\
Processes generator output files in a custom way, saves results inplace.
Replacement token: '%(repl_token)s'.
Arg separator token: '%(args_token)s'.
Replaces the following patterns in files:
'%(repl_token)sREPLACER%(args_token)sARG1%(args_token)sARG2...%(repl_token)s'
->
REPLACER(ARG1, ARG2, ...) value
Available REPLACERs:
%(replacers)s
"""
% {
"repl_token": Processor.REPLACEMENT_TOKEN,
"args_token": Processor.ARGS_TOKEN,
"repl_token": Replacer.REPLACEMENT_TOKEN,
"args_token": Replacer.ARGS_TOKEN,
"replacers": "\n ".join(Replacer.allowed_actions),
},
)
parser.add_argument("--schema", required=True)
parser.add_argument("--input-path", required=True)
parser.add_argument("--schema", required=True, help="Path to server schema yaml")
parser.add_argument("--input-path", required=True, help="Path to target file or directory")
parser.add_argument(
"--file-ext",
default=".py",
help="If working on a directory, look for "
"files with the specified extension (default: %(default)s)",
)
return parser.parse_args(args)
@ -145,10 +155,10 @@ def main(args=None):
args = parse_args(args)
schema = parse_schema(args.schema)
processor = Processor(schema=schema)
processor = Replacer(schema=schema)
if osp.isdir(args.input_path):
processor.process_dir(args.input_path)
processor.process_dir(args.input_path, file_ext=args.file_ext)
elif osp.isfile(args.input_path):
processor.process_file(args.input_path)

@ -0,0 +1,26 @@
# SDK for [Computer Vision Annotation Tool (CVAT)](https://github.com/cvat-ai/cvat)
This package provides a Python client library for CVAT server. It can be useful for
workflow automation and writing custom CVAT server clients.
The SDK API includes 2 layers:
- Server API wrappers (`ApiClient`). Located in at `cvat_sdk.api_client`
- High-level tools (`Core`). Located at `cvat_sdk.core`
Package documentation is available [here](https://opencv.github.io/cvat/docs/api_sdk/sdk).
## Installation & Usage
To install a prebuilt package, run the following command in the terminal:
```bash
pip install cvat-sdk
```
To install from the local directory, follow [the developer guide](https://opencv.github.io/cvat/docs/api_sdk/sdk/developer_guide).
After installation you can import the package:
```python
import cvat_sdk
```

@ -23,7 +23,7 @@ To install a prebuilt package, run the following command in the terminal:
pip install cvat-sdk
```
To install from the local directory, follow [the developer guide](https://opencv.github.io/cvat/docs/integration/sdk/developer_guide).
To install from the local directory, follow [the developer guide](https://opencv.github.io/cvat/docs/api_sdk/sdk/developer_guide).
After installation you can import the package:

@ -155,16 +155,16 @@ with make_client(host="{{{basePath}}}") as client:
task.remove()
```
## Documentation for API Endpoints
## Available API Endpoints
All URIs are relative to *{{basePath}}*
All URIs are relative to _{{basePath}}_
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | **{{operationId}}** | **{{httpMethod}}** {{path}} | {{summary}}
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}_{{classname}}_ | [**{{>operation_name}}**](apis/{{classname}}#{{>operation_name}}) | **{{httpMethod}}** {{path}} | {{summary}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
## Documentation For Models
## Available Models
{{#models}}{{#model}} - {{{classname}}}
{{/model}}{{/models}}

@ -0,0 +1,59 @@
# {{classname}}
{{#description}}{{.}}
{{/description}}
All URIs are relative to _{{basePath}}_
Method | HTTP request | Description
------------- | ------------- | -------------
{{#operations}}{{#operation}}[**{{>operation_name}}**]({{classname}}#{{>operation_name}}) | **{{httpMethod}}** {{path}} | {{summary}}
{{/operation}}{{/operations}}
{{#operations}}
{{#operation}}
## **{{>operation_name}}**
> {{#returnType}}{{{.}}} {{/returnType}}{{>operation_name}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}})
{{{summary}}}{{#notes}}
{{{.}}}{{/notes}}
### Example
{{> api_doc_example }}
### Parameters
{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
{{#requiredParams}}{{^defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} |
{{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} | defaults to {{{.}}}
{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} | [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}}
{{/optionalParams}}
### Return type
{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**](../models/{{returnBaseType}}){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}None (empty response body){{/returnType}}
### Authorization
{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}{{{name}}}{{^-last}}, {{/-last}}{{/authMethods}}
### HTTP request headers
- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}
{{#responses.0}}
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
{{#responses}}
**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}} <br> {{/headers}}{{^headers.0}} - {{/headers.0}} |
{{/responses}}
{{/responses.0}}
{{/operation}}
{{/operations}}

@ -0,0 +1,44 @@
```python
import time
from {{{packageName}}} import Configuration, ApiClient, exceptions
{{#imports}}
{{.}}
{{/imports}}
from pprint import pprint
# Set up an API client
# Read Configuration class docs for more info about parameters and authentication methods
configuration = Configuration(
host = "{{{basePath}}}",{{#hasAuthMethods}}
{{#authMethods}}
{{#isBasic}}
{{#isBasicBasic}}
username = 'YOUR_USERNAME',
password = 'YOUR_PASSWORD',
{{/isBasicBasic}}
{{/isBasic}}
{{/authMethods}}
{{/hasAuthMethods}}
)
with ApiClient(configuration) as api_client:
{{#requiredParams}}
{{^defaultValue}}
{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}
{{/defaultValue}}
{{/requiredParams}}
{{#optionalParams}}
{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}}
{{/optionalParams}}
try:
{{#returnType}}(data, response) = {{/returnType}}%%%make_api_name!!!{{classname}}%%%.{{{operationId}}}({{#requiredParams}}
{{^defaultValue}}{{paramName}},{{/defaultValue}}{{/requiredParams}}{{#optionalParams}}
{{paramName}}={{paramName}},{{#-last}}
{{/-last}}{{/optionalParams}})
{{#returnType}}
pprint(data)
{{/returnType}}
except exceptions.ApiException as e:
print("Exception when calling {{classname}}.{{operationId}}: %s\n" % e)
```

@ -0,0 +1,32 @@
{{#models}}{{#model}}# {{classname}}
{{#description}}{{&description}}
{{/description}}
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
{{#isEnum}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}{{#allowableValues}}{{#defaultValue}}, {{/defaultValue}} must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}}
{{/isEnum}}
{{#isAlias}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}
{{/isAlias}}
{{#isArray}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}}{{#arrayModelType}}[**{{dataType}}**]({{arrayModelType}}){{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}
{{/isArray}}
{{#requiredVars}}
{{^defaultValue}}
**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | {{#isReadOnly}}[readonly] {{/isReadOnly}}
{{/defaultValue}}
{{/requiredVars}}
{{#requiredVars}}
{{#defaultValue}}
**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}defaults to {{{.}}}{{/defaultValue}}
{{/defaultValue}}
{{/requiredVars}}
{{#optionalVars}}
**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | [optional] {{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}}
{{/optionalVars}}
{{/model}}{{/models}}

@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: MIT
import textwrap
from typing import Type
from rest_framework import serializers
from drf_spectacular.extensions import OpenApiSerializerExtension
@ -78,6 +79,8 @@ class WriteOnceSerializerExtension(OpenApiSerializerExtension):
"""
Enables support for cvat.apps.engine.serializers.WriteOnceMixin in drf-spectacular.
Doesn't block other extensions on the target serializer.
Removes the WriteOnceMixin class docstring from derived class descriptions.
"""
match_subclasses = True
@ -94,13 +97,20 @@ class WriteOnceSerializerExtension(OpenApiSerializerExtension):
return False
def map_serializer(self, auto_schema, direction):
return auto_schema._map_serializer(
from cvat.apps.engine.serializers import WriteOnceMixin
schema = auto_schema._map_serializer(
_copy_serializer(self.target, context={
'view': auto_schema.view,
self._PROCESSED_INDICATOR_NAME: True
}),
direction, bypass_extensions=False)
if schema.get('description') == textwrap.dedent(WriteOnceMixin.__doc__).strip():
del schema['description']
return schema
class OpenApiTypeProxySerializerExtension(PolymorphicProxySerializerExtension):
"""
Provides support for OpenApiTypes in the PolymorphicProxySerializer list

@ -11,8 +11,8 @@ SDK is a Python library. It provides you access to Python function and objects,
simplify server interaction and provide additional functionality like data validation.
SDK API includes 2 layers:
- Low-level API with REST API wrappers. Located in at `cvat_sdk.api_client`. [Read more](/api_sdk/sdk/lowlevel-api)
- High-level API. Located at `cvat_sdk.core`. [Read more](/api_sdk/sdk/highlevel-api)
- Low-level API with REST API wrappers. Located in at `cvat_sdk.api_client`. [Read more](../sdk/lowlevel-api)
- High-level API. Located at `cvat_sdk.core`. [Read more](../sdk/highlevel-api)
Roughly, low-level API provides single-request operations, while the high-level one allows you
to use composite, multi-request operations and have local counterparts for server objects.

@ -7,7 +7,7 @@ description: ''
## Overview
This package contains manually written and generated files. We store only sources in
This package contains manually written and autogenerated files. We store only sources in
the repository. To get the full package, one need to generate missing package files.
## Package file layout
@ -23,7 +23,7 @@ the repository. To get the full package, one need to generate missing package fi
If you have a local custom version of the server, run the following command in the terminal.
You need to be able to execute django server. Server installation instructions are available
[here](/contributing/development-environment).
[here](/docs/contributing/development-environment).
```bash
mkdir -p cvat-sdk/schema/ && python manage.py spectacular --file cvat-sdk/schema/schema.yml
```
@ -41,6 +41,10 @@ you can also get schema from `<yourserver>/api/docs`:
![Download server schema button image](/images/download_server_schema.png)
The official server schema for `app.cvat.ai` is available [here](https://app.cvat.ai/api/docs/).
You can read more about server schema [here](/docs/api_sdk/api#api-schema).
2. Install generator dependencies:
```bash
pip install -r gen/requirements.txt
@ -78,3 +82,72 @@ To execute, run:
```bash
pytest tests/python/rest_api tests/python/sdk
```
## SDK API design decisions
The generated `ApiClient` code is modified from what `openapi-generator` does by default.
Changes are mostly focused on better user experience - including better
usage patterns and simpler/faster ways to achieve results.
### Modifications
- Added Python type annotations for return types and class members.
This change required us to implement a custom post-processing script,
which converts generated types into correct type annotations. The types
generated by default are supposed to work with the API implementation
(parameter validation and parsing), but they are not applicable as
type annotations (they have incorrect syntax). Custom post-processing
allowed us to make these types correct type annotations.
Other possible solutions:
- There is the `python-experimental` API generator, which may solve
some issues, but it is unstable and requires python 3.9. Our API
works with 3.7, which is the lowest supported version now.
- Custom templates - partially works, but only in limited cases
(model fields). It's very hard to maintain the template code and
logic for this. Only `if` checks and `for` loops are available in
mustache templates, which is not enough for annotation generation.
- Separate APIs are embedded into the general `APIClient` class.
Now we have:
```python
with ApiClient(config) as api_client:
result1 = api_client.foo_api.operation1()
result2 = api_client.bar_api.operation2()
```
This showed to be more convenient than the default:
```python
with ApiClient(config) as api_client:
foo_api = FooApi(api_client)
result1 = foo_api.operation1()
result2 = foo_api.operation2()
bar_api = BarApi(api_client)
result3 = bar_api.operation3()
result4 = bar_api.operation4()
```
This also required custom post-processing. Operation Ids are
[supposed to be unique](https://swagger.io/specification/#operation-object)
in the OpenAPI / Swagger specification. Therefore, we can't generate such
schema on the server, nor we can't expect it to be supported in the
API generator.
- Operations have IDs like `<api>/<method>_<object>`.
This also showed to be more readable and more natural than DRF-spectacular's
default `<api>/<object>_<method>`.
- Server operations have different types for input and output values.
While it can be expected that an endopint with POST/PUT methods available
(like `create` or `partial_update`) has the same type for input and output
(because it looks natural), it also leads to the situation, in which there
are lots of read-/write-only fields, and it becomes hard for understanding.
This clear type separation is supposed to make it simpler for users.
- Added cookie management in the `ApiClient` class.
- Added interface classes for models to simplify class member usage and lookup.
- Dicts can be passed into API methods and model constructors instead of models.
They are automatically parsed as models. In the original implementation, the user
is required to pass a `Configuration` object each time, which is clumsy and adds little sense.

@ -7,7 +7,7 @@ description: ''
## Overview
Low-level API is useful if you need to work directly with REST API, but want
The low-level API is useful if you need to work directly with REST API, but want
to have data validation and syntax assistance from your code editor. The code
on this layer is autogenerated.
@ -103,146 +103,119 @@ with ApiClient(configuration) as api_client:
assert task.size == 4
```
## Available API Endpoints
## ApiClient and configuration
All URIs are relative to _`http://localhost`_
The starting point in the low-level API is the `cvat_sdk.api_client.ApiClient` class.
It encapsulates session and connection logic, manages headers and cookies,
and provides access to various APIs.
APIs can be instanted directly like this:
To create an instance of `ApiClient`, you need to set up a `cvat_sdk.api_client.Configuration`
object and pass it to the `ApiClient` class constructor. Additional connection-specific
options, such as extra headers and cookies can be specified in the class constructor.
`ApiClient` implements the context manager protocol. Typically, you create `ApiClient` this way:
```python
from cvat_sdk.api_client import ApiClient, Configuration
configuration = Configuration(host="http://localhost")
with ApiClient(configuration) as api_client:
...
```
After creating an `ApiClient` instance, you can send requests to various server endpoints
via `*_api` member properties and directly, using the `rest_client` member.
[Read more](#api-wrappers) about API wrappers below.
Typically, the first thing you do with `ApiClient` is log in.
[Read more](#authentication) about authentication options below.
## Authentication
CVAT supports 2 authentication options:
- basic auth, with your username and password
- token auth, with your API key
Token auth requires a token, which can be obtained after performing the basic auth.
The low-level API supports 2 ways of authentication.
You can specify authentication parameters in the `Configuration` object:
```python
configuration = Configuration(
username='YOUR_USERNAME',
password='YOUR_PASSWORD',
)
```
```python
configuration = Configuration(
api_key={
"sessionAuth": "<sessionid cookie value>",
"csrfAuth": "<csrftoken cookie value>",
"tokenAuth": "Token <auth key value>",
}
)
```
You can perform a regular login using the `auth_api` member of `ApiClient` and
set the `Authorization` header using the `Token` prefix. This way, you'll be able to
obtain API tokens, which can be reused in the future to avoid typing your credentials.
```python
from cvat_sdk.api_client import models
(auth, _) = api_client.auth_api.create_login(
models.LoginRequest(username=credentials[0], password=credentials[1])
)
assert "sessionid" in api_client.cookies
assert "csrftoken" in api_client.cookies
api_client.set_default_header("Authorization", "Token " + auth.key)
```
## API wrappers
API endpoints are grouped by tags into separate classes in the `cvat_sdk.api_client.apis` package.
APIs can be accessed as `ApiClient` object members:
```python
api_client.auth_api.<operation>(...)
api_client.tasks_api.<operation>(...)
```
And APIs can be instantiated directly like this:
```python
from cvat_sdk.api_client import ApiClient, apis
api_client = ApiClient(...)
auth_api = apis.AuthApi(api_client)
auth_api.<operation>(...)
```
Or they can be accessed as `ApiClient` object members:
tasks_api = apis.TasksApi(api_client)
tasks_api.<operation>(...)
```
from cvat_sdk.api_client import ApiClient
api_client = ApiClient(...)
api_client.auth_api.<operation>(...)
```
For each operation, the API wrapper class has a corresponding `<operation>_endpoint` member.
This member represents the endpoint as a first-class object, which provides metainformation
about the endpoint, such as the relative URL of the endpoint, parameter names,
types and their placement in the request. It also allows to pass the operation to other
functions and invoke it from there.
For a typical server entity like `Task`, `Project`, `Job` etc., the `*Api` classes provide methods
that reflect Create-Read-Update-Delete (CRUD) operations: `create`, `retrieve`, `list`, `update`,
`partial_update`, `delete`. The set of available operations depends on the entity type.
You can find the list of the available APIs and their documentation [here](../reference/apis/).
<!--lint disable table-cell-padding-->
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
_AuthApi_ | **auth_create_login** | **POST** /api/auth/login |
_AuthApi_ | **auth_create_logout** | **POST** /api/auth/logout |
_AuthApi_ | **auth_create_password_change** | **POST** /api/auth/password/change |
_AuthApi_ | **auth_create_password_reset** | **POST** /api/auth/password/reset |
_AuthApi_ | **auth_create_password_reset_confirm** | **POST** /api/auth/password/reset/confirm |
_AuthApi_ | **auth_create_register** | **POST** /api/auth/register |
_AuthApi_ | **auth_create_signing** | **POST** /api/auth/signing | This method signs URL for access to the server
_CloudstoragesApi_ | **cloudstorages_create** | **POST** /api/cloudstorages | Method creates a cloud storage with a specified characteristics
_CloudstoragesApi_ | **cloudstorages_destroy** | **DELETE** /api/cloudstorages/{id} | Method deletes a specific cloud storage
_CloudstoragesApi_ | **cloudstorages_list** | **GET** /api/cloudstorages | Returns a paginated list of storages according to query parameters
_CloudstoragesApi_ | **cloudstorages_partial_update** | **PATCH** /api/cloudstorages/{id} | Methods does a partial update of chosen fields in a cloud storage instance
_CloudstoragesApi_ | **cloudstorages_retrieve** | **GET** /api/cloudstorages/{id} | Method returns details of a specific cloud storage
_CloudstoragesApi_ | **cloudstorages_retrieve_actions** | **GET** /api/cloudstorages/{id}/actions | Method returns allowed actions for the cloud storage
_CloudstoragesApi_ | **cloudstorages_retrieve_content** | **GET** /api/cloudstorages/{id}/content | Method returns a manifest content
_CloudstoragesApi_ | **cloudstorages_retrieve_preview** | **GET** /api/cloudstorages/{id}/preview | Method returns a preview image from a cloud storage
_CloudstoragesApi_ | **cloudstorages_retrieve_status** | **GET** /api/cloudstorages/{id}/status | Method returns a cloud storage status
_CommentsApi_ | **comments_create** | **POST** /api/comments | Method creates a comment
_CommentsApi_ | **comments_destroy** | **DELETE** /api/comments/{id} | Method deletes a comment
_CommentsApi_ | **comments_list** | **GET** /api/comments | Method returns a paginated list of comments according to query parameters
_CommentsApi_ | **comments_partial_update** | **PATCH** /api/comments/{id} | Methods does a partial update of chosen fields in a comment
_CommentsApi_ | **comments_retrieve** | **GET** /api/comments/{id} | Method returns details of a comment
_InvitationsApi_ | **invitations_create** | **POST** /api/invitations | Method creates an invitation
_InvitationsApi_ | **invitations_destroy** | **DELETE** /api/invitations/{key} | Method deletes an invitation
_InvitationsApi_ | **invitations_list** | **GET** /api/invitations | Method returns a paginated list of invitations according to query parameters
_InvitationsApi_ | **invitations_partial_update** | **PATCH** /api/invitations/{key} | Methods does a partial update of chosen fields in an invitation
_InvitationsApi_ | **invitations_retrieve** | **GET** /api/invitations/{key} | Method returns details of an invitation
_IssuesApi_ | **issues_create** | **POST** /api/issues | Method creates an issue
_IssuesApi_ | **issues_destroy** | **DELETE** /api/issues/{id} | Method deletes an issue
_IssuesApi_ | **issues_list** | **GET** /api/issues | Method returns a paginated list of issues according to query parameters
_IssuesApi_ | **issues_list_comments** | **GET** /api/issues/{id}/comments | The action returns all comments of a specific issue
_IssuesApi_ | **issues_partial_update** | **PATCH** /api/issues/{id} | Methods does a partial update of chosen fields in an issue
_IssuesApi_ | **issues_retrieve** | **GET** /api/issues/{id} | Method returns details of an issue
_JobsApi_ | **jobs_create_annotations** | **POST** /api/jobs/{id}/annotations/ | Method allows to upload job annotations
_JobsApi_ | **jobs_destroy_annotations** | **DELETE** /api/jobs/{id}/annotations/ | Method deletes all annotations for a specific job
_JobsApi_ | **jobs_list** | **GET** /api/jobs | Method returns a paginated list of jobs according to query parameters
_JobsApi_ | **jobs_list_commits** | **GET** /api/jobs/{id}/commits | The action returns the list of tracked changes for the job
_JobsApi_ | **jobs_list_issues** | **GET** /api/jobs/{id}/issues | Method returns list of issues for the job
_JobsApi_ | **jobs_partial_update** | **PATCH** /api/jobs/{id} | Methods does a partial update of chosen fields in a job
_JobsApi_ | **jobs_partial_update_annotations** | **PATCH** /api/jobs/{id}/annotations/ | Method performs a partial update of annotations in a specific job
_JobsApi_ | **jobs_partial_update_annotations_file** | **PATCH** /api/jobs/{id}/annotations/{file_id} | Allows to upload an annotation file chunk. Implements TUS file uploading protocol.
_JobsApi_ | **jobs_retrieve** | **GET** /api/jobs/{id} | Method returns details of a job
_JobsApi_ | **jobs_retrieve_annotations** | **GET** /api/jobs/{id}/annotations/ | Method returns annotations for a specific job as a JSON document. If format is specified a zip archive is returned.
_JobsApi_ | **jobs_retrieve_data** | **GET** /api/jobs/{id}/data | Method returns data for a specific job
_JobsApi_ | **jobs_retrieve_data_meta** | **GET** /api/jobs/{id}/data/meta | Method provides a meta information about media files which are related with the job
_JobsApi_ | **jobs_retrieve_dataset** | **GET** /api/jobs/{id}/dataset | Export job as a dataset in a specific format
_JobsApi_ | **jobs_update_annotations** | **PUT** /api/jobs/{id}/annotations/ | Method performs an update of all annotations in a specific job
_LambdaApi_ | **lambda_create_functions** | **POST** /api/lambda/functions/{func_id} |
_LambdaApi_ | **lambda_create_requests** | **POST** /api/lambda/requests | Method calls the function
_LambdaApi_ | **lambda_list_functions** | **GET** /api/lambda/functions | Method returns a list of functions
_LambdaApi_ | **lambda_list_requests** | **GET** /api/lambda/requests | Method returns a list of requests
_LambdaApi_ | **lambda_retrieve_functions** | **GET** /api/lambda/functions/{func_id} | Method returns the information about the function
_LambdaApi_ | **lambda_retrieve_requests** | **GET** /api/lambda/requests/{id} | Method returns the status of the request
_MembershipsApi_ | **memberships_destroy** | **DELETE** /api/memberships/{id} | Method deletes a membership
_MembershipsApi_ | **memberships_list** | **GET** /api/memberships | Method returns a paginated list of memberships according to query parameters
_MembershipsApi_ | **memberships_partial_update** | **PATCH** /api/memberships/{id} | Methods does a partial update of chosen fields in a membership
_MembershipsApi_ | **memberships_retrieve** | **GET** /api/memberships/{id} | Method returns details of a membership
_OrganizationsApi_ | **organizations_create** | **POST** /api/organizations | Method creates an organization
_OrganizationsApi_ | **organizations_destroy** | **DELETE** /api/organizations/{id} | Method deletes an organization
_OrganizationsApi_ | **organizations_list** | **GET** /api/organizations | Method returns a paginated list of organizatins according to query parameters
_OrganizationsApi_ | **organizations_partial_update** | **PATCH** /api/organizations/{id} | Methods does a partial update of chosen fields in an organization
_OrganizationsApi_ | **organizations_retrieve** | **GET** /api/organizations/{id} | Method returns details of an organization
_ProjectsApi_ | **projects_create** | **POST** /api/projects | Method creates a new project
_ProjectsApi_ | **projects_create_backup** | **POST** /api/projects/backup/ | Methods create a project from a backup
_ProjectsApi_ | **projects_create_dataset** | **POST** /api/projects/{id}/dataset/ | Import dataset in specific format as a project
_ProjectsApi_ | **projects_destroy** | **DELETE** /api/projects/{id} | Method deletes a specific project
_ProjectsApi_ | **projects_list** | **GET** /api/projects | Returns a paginated list of projects according to query parameters (12 projects per page)
_ProjectsApi_ | **projects_list_tasks** | **GET** /api/projects/{id}/tasks | Method returns information of the tasks of the project with the selected id
_ProjectsApi_ | **projects_partial_update** | **PATCH** /api/projects/{id} | Methods does a partial update of chosen fields in a project
_ProjectsApi_ | **projects_partial_update_backup_file** | **PATCH** /api/projects/backup/{file_id} | Allows to upload a file chunk. Implements TUS file uploading protocol
_ProjectsApi_ | **projects_partial_update_dataset_file** | **PATCH** /api/projects/{id}/dataset/{file_id} | Allows to upload a file chunk. Implements TUS file uploading protocol.
_ProjectsApi_ | **projects_retrieve** | **GET** /api/projects/{id} | Method returns details of a specific project
_ProjectsApi_ | **projects_retrieve_annotations** | **GET** /api/projects/{id}/annotations | Method allows to download project annotations
_ProjectsApi_ | **projects_retrieve_backup** | **GET** /api/projects/{id}/backup | Methods creates a backup copy of a project
_ProjectsApi_ | **projects_retrieve_dataset** | **GET** /api/projects/{id}/dataset/ | Export project as a dataset in a specific format
_RestrictionsApi_ | **restrictions_retrieve_terms_of_use** | **GET** /api/restrictions/terms-of-use | Method provides CVAT terms of use
_RestrictionsApi_ | **restrictions_retrieve_user_agreements** | **GET** /api/restrictions/user-agreements | Method provides user agreements that the user must accept to register
_SchemaApi_ | **schema_retrieve** | **GET** /api/schema/ |
_ServerApi_ | **server_create_exception** | **POST** /api/server/exception | Method saves an exception from a client on the server
_ServerApi_ | **server_create_logs** | **POST** /api/server/logs | Method saves logs from a client on the server
_ServerApi_ | **server_list_share** | **GET** /api/server/share | Returns all files and folders that are on the server along specified path
_ServerApi_ | **server_retrieve_about** | **GET** /api/server/about | Method provides basic CVAT information
_ServerApi_ | **server_retrieve_annotation_formats** | **GET** /api/server/annotation/formats | Method provides the list of supported annotations formats
_ServerApi_ | **server_retrieve_plugins** | **GET** /api/server/plugins | Method provides allowed plugins
_TasksApi_ | **jobs_partial_update_data_meta** | **PATCH** /api/jobs/{id}/data/meta | Method provides a meta information about media files which are related with the job
_TasksApi_ | **tasks_create** | **POST** /api/tasks | Method creates a new task in a database without any attached images and videos
_TasksApi_ | **tasks_create_annotations** | **POST** /api/tasks/{id}/annotations/ | Method allows to upload task annotations from a local file or a cloud storage
_TasksApi_ | **tasks_create_backup** | **POST** /api/tasks/backup/ | Method recreates a task from an attached task backup file
_TasksApi_ | **tasks_create_data** | **POST** /api/tasks/{id}/data/ | Method permanently attaches images or video to a task. Supports tus uploads, see more <https://tus.io/>
_TasksApi_ | **tasks_destroy** | **DELETE** /api/tasks/{id} | Method deletes a specific task, all attached jobs, annotations, and data
_TasksApi_ | **tasks_destroy_annotations** | **DELETE** /api/tasks/{id}/annotations/ | Method deletes all annotations for a specific task
_TasksApi_ | **tasks_list** | **GET** /api/tasks | Returns a paginated list of tasks according to query parameters (10 tasks per page)
_TasksApi_ | **tasks_list_jobs** | **GET** /api/tasks/{id}/jobs | Method returns a list of jobs for a specific task
_TasksApi_ | **tasks_partial_update** | **PATCH** /api/tasks/{id} | Methods does a partial update of chosen fields in a task
_TasksApi_ | **tasks_partial_update_annotations** | **PATCH** /api/tasks/{id}/annotations/ | Method performs a partial update of annotations in a specific task
_TasksApi_ | **tasks_partial_update_annotations_file** | **PATCH** /api/tasks/{id}/annotations/{file_id} | Allows to upload an annotation file chunk. Implements TUS file uploading protocol.
_TasksApi_ | **tasks_partial_update_backup_file** | **PATCH** /api/tasks/backup/{file_id} | Allows to upload a file chunk. Implements TUS file uploading protocol.
_TasksApi_ | **tasks_partial_update_data_file** | **PATCH** /api/tasks/{id}/data/{file_id} | Allows to upload a file chunk. Implements TUS file uploading protocol.
_TasksApi_ | **tasks_partial_update_data_meta** | **PATCH** /api/tasks/{id}/data/meta | Method provides a meta information about media files which are related with _he_task
_TasksApi_ | **tasks_retrieve** | **GET** /api/tasks/{id} | Method returns details of a specific task
_TasksApi_ | **tasks_retrieve_annotations** | **GET** /api/tasks/{id}/annotations/ | Method allows to download task annotations
_TasksApi_ | **tasks_retrieve_backup** | **GET** /api/tasks/{id}/backup | Method backup a specified task
_TasksApi_ | **tasks_retrieve_data** | **GET** /api/tasks/{id}/data/ | Method returns data for a specific task
_TasksApi_ | **tasks_retrieve_data_meta** | **GET** /api/tasks/{id}/data/meta | Method provides a meta information about media files which are related with the task
_TasksApi_ | **tasks_retrieve_dataset** | **GET** /api/tasks/{id}/dataset | Export task as a dataset in a specific format
_TasksApi_ | **tasks_retrieve_status** | **GET** /api/tasks/{id}/status | When task is being created the method returns information about a status of the creation process
_TasksApi_ | **tasks_update_annotations** | **PUT** /api/tasks/{id}/annotations/ | Method allows to upload task annotations
_UsersApi_ | **users_destroy** | **DELETE** /api/users/{id} | Method deletes a specific user from the server
_UsersApi_ | **users_list** | **GET** /api/users | Method provides a paginated list of users registered on the server
_UsersApi_ | **users_partial_update** | **PATCH** /api/users/{id} | Method updates chosen fields of a user
_UsersApi_ | **users_retrieve** | **GET** /api/users/{id} | Method provides information of a specific user
_UsersApi_ | **users_retrieve_self** | **GET** /api/users/self | Method returns an instance of a user who is currently authorized
## Available Models
## Models
Requests and responses can include data. It can be represented as plain Python
data structures and model classes (or models). In CVAT API, model for requests and responses
are separated: the request models have the `Request` suffix in the name, while the response
models have no suffix. Models can be found in the `cvat_sdk.api_client.models` package.
Models can be instantiated like this:
@ -252,135 +225,141 @@ from cvat_sdk.api_client import models
user_model = models.User(...)
```
- About
- AnnotationFileRequest
- AnnotationsRead
- Attribute
- AttributeRequest
- AttributeVal
- AttributeValRequest
- BackupWriteRequest
- BasicUser
- BasicUserRequest
- ChunkType
- CloudStorageRead
- CloudStorageWriteRequest
- CommentRead
- CommentReadOwner
- CommentWriteRequest
- CredentialsTypeEnum
- DataMetaRead
- DataRequest
- DatasetFileRequest
- DatasetFormat
- DatasetFormats
- DatasetWriteRequest
- Exception
- ExceptionRequest
- FileInfo
- FileInfoTypeEnum
- FrameMeta
- InputTypeEnum
- InvitationRead
- InvitationWrite
- InvitationWriteRequest
- IssueRead
- IssueWriteRequest
- JobAnnotationsUpdateRequest
- JobCommit
- JobRead
- JobStage
- JobStatus
- Label
- LabeledData
- LabeledDataRequest
- LabeledImage
- LabeledImageRequest
- LabeledShape
- LabeledShapeRequest
- LabeledTrack
- LabeledTrackRequest
- LocationEnum
- LogEvent
- LogEventRequest
- LoginRequest
- Manifest
- ManifestRequest
- MembershipRead
- MembershipWrite
- MetaUser
- OperationStatus
- OrganizationRead
- OrganizationWrite
- OrganizationWriteRequest
- PaginatedCloudStorageReadList
- PaginatedCommentReadList
- PaginatedInvitationReadList
- PaginatedIssueReadList
- PaginatedJobCommitList
- PaginatedJobReadList
- PaginatedMembershipReadList
- PaginatedMetaUserList
- PaginatedPolymorphicProjectList
- PaginatedTaskReadList
- PasswordChangeRequest
- PasswordResetConfirmRequest
- PasswordResetSerializerExRequest
- PatchedCloudStorageWriteRequest
- PatchedCommentWriteRequest
- PatchedDataMetaWriteRequest
- PatchedInvitationWriteRequest
- PatchedIssueWriteRequest
- PatchedJobWriteRequest
- PatchedLabelRequest
- PatchedLabeledDataRequest
- PatchedMembershipWriteRequest
- PatchedOrganizationWriteRequest
- PatchedProjectWriteRequest
- PatchedProjectWriteRequestTargetStorage
- PatchedTaskWriteRequest
- PatchedTaskWriteRequestTargetStorage
- PatchedUserRequest
- Plugins
- PolymorphicProject
- ProjectFileRequest
- ProjectRead
- ProjectReadAssignee
- ProjectReadOwner
- ProjectReadTargetStorage
- ProjectSearch
- ProjectWriteRequest
- ProviderTypeEnum
- RestAuthDetail
- RestrictedRegister
- RestrictedRegisterRequest
- RoleEnum
- RqStatus
- RqStatusStateEnum
- Segment
- ShapeType
- SigningRequest
- SimpleJob
- SortingMethod
- Storage
- StorageMethod
- StorageRequest
- StorageType
- SubLabeledShape
- SubLabeledShapeRequest
- SubLabeledTrack
- SubLabeledTrackRequest
- Sublabel
- SublabelRequest
- TaskAnnotationsUpdateRequest
- TaskAnnotationsWriteRequest
- TaskFileRequest
- TaskRead
- TaskReadTargetStorage
- TaskWriteRequest
- Token
- TrackedShape
- TrackedShapeRequest
- User
- UserAgreement
- UserAgreementRequest
Model parameters can be passed as models, or as plain Python data structures. This rule applies
recursively, starting from the method parameters. In particular, this means you can pass
a dict into a method or into a model constructor, and corresponding fields will
be parsed from this data automatically:
```python
task_spec = models.TaskWriteRequest(
name='example task',
labels=[
models.PatchedLabelRequest(
name="car",
color="#ff00ff",
attributes=[
model.AttributeRequest(
name="a",
mutable=True,
input_type="number",
default_value="5",
values=["4", "5", "6"]
)
]
)
],
)
api_client.tasks_api.create(task_spec)
```
Is equivalent to:
```python
api_client.tasks_api.create({
'name': 'example task',
"labels": [{
"name": "car",
"color": "#ff00ff",
"attributes": [
{
"name": "a",
"mutable": True,
"input_type": "number",
"default_value": "5",
"values": ["4", "5", "6"]
}
]
}],
})
```
You can mix these variants.
Most models provide corresponding interface classes called like `I<model name>`. They can be
used to implement your own classes or describe APIs. They just provide type annotations
and descriptions for model fields.
You can export model values to plain Python dicts using the `as_dict()` method and
the `cvat_sdk.api_client.model_utils.to_json()` function.
You can find the list of the available models and their documentation [here](../reference/models/).
## Sending requests
To send a request to a server endpoint, you need to obtain an instance of the corresponding `*Api`
class. You can find summary about available API classes and supported endpoints
[here](../reference/apis). The `*Api` instance object allows to send requests to the relevant
server endpoints.
By default, all operations return 2 objects: the parsed response data and the response itself.
A typical call looks like this:
```python
from cvat_sdk.api_client import ApiClient, apis
with ApiClient(...) as api_client:
...
(data, response) = api_client.tasks_api.list()
# process the response ...
```
Operation parameters can be passed as positional or keyword arguments. There are also several
extra arguments which change invocation logic:
- `_parse_response` - Allows to enable and disable response data parsing. When enabled,
the response data is parsed into a model or a basic type and returned as the first value.
When disabled, the response is not parsed, and `None` is returned. Can be useful,
for instance, if you need to parse data manually, or if you expect an error in the response.
- `_check_status` - Allows to enable or disable response status checks. When enabled, the
response status code is checked to be positive as defined in the [HTTP standards](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes).
In case of negative status, an exception is raised.
- `_request_timeout` - Allows to control timeout
- `_content_type` - Allows to specify the `Content-Type` header value for the request. Endpoints
can support different content types and behave differently depending on the value. For file
uploads `_content_type="multipart/form-data"` must be specified.
> **NOTE**: the API is autogenerated. In some cases the server API schema may be incomplete
or underspecified. Please report to us all the problems found. A typical problem is that a
response data can't be parsed automatically due to the incorrect schema. In this case, the
simplest workaround is to disable response parsing using the `_parse_response=False`
method argument.
You can find many examples of API client usage in REST API tests [here](https://github.com/opencv/cvat/tree/develop/tests/python).
### Organizations
To call an operation in the context of an organization, use one of these method arguments:
- `org` - The unique organization slug
- `org_id`- The organization id
```python
...
(updated_annotations, response) = api_client.tasks_api.partial_update_annotations(
id=task_id,
org_id=org_id,
action='update',
patched_labeled_data_request=data
)
```
### Paginated responses
There are several endpoints that allow to request multiple server entities. Typically, these
endpoints are called `list_...`. When there are lots of data, the responses can be paginated to
reduce server load. If an endpoint returns paginated data, a single page is returned per request.
In some cases all entries need to be retrieved. CVAT doesn't provide specific API or parameters
for this, so the solution is to write a loop to collect and join data from multiple requests.
SDK provides an utility function for this at `cvat_sdk.core.helpers.get_paginated_collection()`.
Example:
```python
from cvat_sdk.core.helpers import get_paginated_collection
...
project_tasks = get_paginated_collection(
api_client.projects_api.list_tasks_endpoint,
id=project_id,
)
```

@ -0,0 +1,6 @@
---
title: "SDK API Reference"
linkTitle: "API Reference"
weight: 1
description: ''
---

@ -0,0 +1,2 @@
# The files are autogenerated here
*.md

@ -0,0 +1,3 @@
# The files are autogenerated here
*.md
!_index.md

@ -0,0 +1,6 @@
---
title: 'Models'
linkTitle: 'Models'
weight: 1
description: ''
---

@ -0,0 +1,295 @@
#!/usr/bin/env python
# Copyright (C) 2022 CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
import argparse
import os
import os.path as osp
import re
import shutil
import sys
import textwrap
from glob import iglob
from typing import Callable, List
from inflection import underscore
class Processor:
_reference_files: List[str]
def __init__(self, *, input_dir: str, site_root: str) -> None:
self._input_dir = input_dir
self._site_root = site_root
self._content_dir = osp.join(self._site_root, "content")
self._sdk_reference_dir = osp.join(
self._content_dir, "en/docs/api_sdk/sdk/reference"
)
self._templates_dir = osp.join(self._site_root, "templates")
@staticmethod
def _copy_files(src_dir: str, glob_pattern: str, dst_dir: str) -> List[str]:
copied_files = []
for src_path in iglob(osp.join(src_dir, glob_pattern), recursive=True):
src_filename = osp.relpath(src_path, src_dir)
dst_path = osp.join(dst_dir, src_filename)
# assume dst dir exists
shutil.copy(src_path, dst_path, follow_symlinks=True)
copied_files.append(dst_path)
return copied_files
def _copy_pages(self):
self._reference_files = self._copy_files(
self._input_dir, "*/**/*.md", self._sdk_reference_dir
)
def _add_page_headers(self):
"""
Adds headers required by hugo to docs pages
"""
HEADER_SEPARATOR = "---"
for p in self._reference_files:
with open(p) as f:
contents = f.read()
assert not contents.startswith(HEADER_SEPARATOR), p
lines = contents.splitlines()
assert lines[0].startswith("#")
classname = lines[0][1:].strip()
header = textwrap.dedent(
"""\
%(header_separator)s
title: '%(classname)s class reference'
linkTitle: '%(classname)s'
weight: 10
description: ''
%(header_separator)s
"""
% {"header_separator": HEADER_SEPARATOR, "classname": classname}
)
contents = header + "\n".join(lines[1:])
with open(p, "w") as f:
f.write(contents)
def _extract_apis_summary(self, readme_path: str) -> str:
with open(readme_path) as f:
readme_contents = f.read()
apis_summary = re.search(
r"## Available API Endpoints(.*)## Available Models",
readme_contents,
flags=re.DOTALL,
)[1]
assert len(apis_summary) > 0
return apis_summary
def _move_api_summary(self):
"""
Moves API summary section from README to apis/_index
"""
SUMMARY_REPLACE_TOKEN = "{{REPLACEME:apis_summary}}" # nosec
apis_summary = self._extract_apis_summary(
osp.join(self._input_dir, "README.md")
)
apis_index_filename = osp.join(
osp.relpath(self._sdk_reference_dir, self._content_dir), "apis/_index.md"
)
apis_index_path = osp.join(
self._templates_dir, apis_index_filename + ".template"
)
with open(apis_index_path) as f:
contents = f.read()
contents = contents.replace(SUMMARY_REPLACE_TOKEN, apis_summary)
with open(osp.join(self._content_dir, apis_index_filename), "w") as f:
f.write(contents)
def _fix_page_links_and_references(self):
"""
Replaces reference page links from full lowercase (which is generated by hugo from the
orignal camelcase and creates broken links) ('authapi') to the minus-case ('auth-api'),
which is more readable and works.
Adds an extra parent directory part to links ('../') as hugo requires, even for neighbor
files.
"""
mapping = {}
for src_path in self._reference_files:
src_filename = osp.relpath(src_path, self._sdk_reference_dir)
dst_filename = underscore(src_filename).replace("_", "-")
dst_path = osp.join(self._sdk_reference_dir, dst_filename)
os.rename(src_path, dst_path)
mapping[src_filename] = dst_filename
self._reference_files = [
osp.join(self._sdk_reference_dir, p) for p in mapping.values()
]
for p in iglob(self._sdk_reference_dir + "/**/*.md", recursive=True):
with open(p) as f:
contents = f.read()
for src_filename, dst_filename in mapping.items():
src_dir, src_filename = osp.split(osp.splitext(src_filename)[0])
dst_filename = osp.basename(osp.splitext(dst_filename)[0])
contents = re.sub(
rf"(\[.*?\]\()((?:\.\./)?(?:{src_dir}/)?){src_filename}((?:#[^\)]*?)?\))",
rf"\1../\2{dst_filename}\3",
contents,
)
with open(p, "w") as f:
f.write(contents)
def _process_non_code_blocks(
self, text: str, handlers: List[Callable[[str], str]]
) -> str:
"""
Allows to process Markdown documents with passed callbacks. Callbacks are only
executed outside code blocks.
"""
used_quotes = ""
block_start_pos = 0
inside_code_block = False
while block_start_pos < len(text):
pattern = re.compile(used_quotes or "```|`")
next_code_block_quote = pattern.search(text, pos=block_start_pos)
if next_code_block_quote is not None:
if not used_quotes:
inside_code_block = False
block_end_pos = next_code_block_quote.start(0)
used_quotes = next_code_block_quote.group(0)
else:
inside_code_block = True
block_end_pos = next_code_block_quote.end(0)
used_quotes = None
else:
block_end_pos = len(text)
if not inside_code_block:
block = text[block_start_pos:block_end_pos]
for handler in handlers:
block = handler(block)
text = text[:block_start_pos] + block + text[block_end_pos:]
block_end_pos = block_start_pos + len(block) + len(used_quotes)
block_start_pos = block_end_pos
return text
def _escape_free_square_brackets(self, text: str) -> str:
return re.sub(r"\[([^\[\]]*?)\]([^\(])", r"\[\1\]\2", text)
def _add_angle_brackets_to_free_links(self, text: str) -> str:
# Adapted from https://stackoverflow.com/a/31952097
URL_REGEX = (
# Scheme (HTTP, HTTPS):
r"(?:https?:\/\/)"
r"(?:"
# www:
r"(?:www\.)?"
# Host and domain (including ccSLD):
r"(?:(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.)+)"
# TLD:
r"(?:[a-zA-Z]{2,6})"
# IP Address:
r"|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
r")"
# Port:
r"(?::\d{1,5})?"
# Query path:
r"(?:(?:\/\S+)*|\/)"
)
text = re.sub(
r"(\A|[\.\s])(" + URL_REGEX + r")([\.\s]|\Z)",
r"\1<\2>\3",
text,
flags=re.MULTILINE,
)
return text
def _fix_parsing_problems(self):
"""
Adds angle brackets to freestanding links, as the linter requires. Such links can appear
from the generated model and api descriptions.
Adds escapes to freestanding square brackets to make parsing correct.
"""
for p in iglob(self._sdk_reference_dir + "/**/*.md", recursive=True):
with open(p) as f:
contents = f.read()
contents = self._process_non_code_blocks(
contents,
[
self._add_angle_brackets_to_free_links,
self._escape_free_square_brackets,
],
)
with open(p, "w") as f:
f.write(contents)
def run(self):
assert osp.isdir(self._input_dir), self._input_dir
assert osp.isdir(self._site_root), self._site_root
assert osp.isdir(self._sdk_reference_dir), self._sdk_reference_dir
assert osp.isdir(self._templates_dir), self._templates_dir
self._copy_pages()
self._move_api_summary()
self._add_page_headers()
self._fix_page_links_and_references()
self._fix_parsing_problems()
def parse_args(args=None):
parser = argparse.ArgumentParser()
parser.add_argument(
"--input-dir",
type=osp.abspath,
default="cvat-sdk/docs/",
help="Path to the cvat-sdk/docs/ directory",
)
parser.add_argument(
"--site-root",
type=osp.abspath,
default="site/",
)
return parser.parse_args(args)
def main(args=None):
args = parse_args(args)
processor = Processor(input_dir=args.input_dir, site_root=args.site_root)
processor.run()
return 0
if __name__ == "__main__":
sys.exit(main())

@ -1,3 +1,6 @@
black>=22.1.0
gitpython
inflection >= 0.5.1
isort>=5.10.1
packaging
toml
toml

@ -0,0 +1,8 @@
---
title: 'APIs'
linkTitle: 'APIs'
weight: 1
description: ''
---
{{REPLACEME:apis_summary}}
Loading…
Cancel
Save