From a9e8225fe3fdbe1e33381af1af1500fc4d9f5c6c Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 29 Mar 2023 14:20:26 +0300 Subject: [PATCH] Fix SDK documentation problems (#5903) Resolves #5871 Related: https://github.com/opencv/cvat/issues/5847, https://github.com/opencv/cvat/issues/5814 This PR makes SDK examples valid Python code and adds some missing info. - Fixed incorrect method names in examples - Added missing `api_client.` use in method examples - Fixed invalid return types in examples - Fixed imports in examples - Added notes about binary files in low-level API, extra method args and returned values - Removed html escapes from method and field descriptions - Changed function signatures to be closer to the most popular variants --- .../openapi-generator/api_doc.mustache | 30 +++- .../api_doc_example.mustache | 12 +- .../openapi-generator/model_doc.mustache | 14 +- .../en/docs/api_sdk/sdk/lowlevel-api.md | 140 ++++++++++++++++-- 4 files changed, 165 insertions(+), 31 deletions(-) diff --git a/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache b/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache index ed8d97d51..95be916e1 100644 --- a/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache +++ b/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache @@ -13,7 +13,13 @@ Method | HTTP request | Description {{#operations}} {{#operation}} ## **{{>operation_name}}** -> {{#returnType}}{{{.}}} {{/returnType}}{{>operation_name}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}}) + +> {{>operation_name}}({{#requiredParams}}{{^defaultValue}} +> {{paramName}},{{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}} +> {{paramName}}={{{defaultValue}}},{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} +> {{paramName}}=None,{{/optionalParams}} +> **kwargs +> ) {{{summary}}}{{#notes}} @@ -26,14 +32,24 @@ Method | HTTP request | Description {{^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}} +{{#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 +There are also optional kwargs that control the function invocation behavior. +[Read more here](https://opencv.github.io/cvat/docs/api_sdk/sdk/lowlevel-api/#sending-requests). + +### Returned values + +Returned type: `Tuple[{{>return_type}}, urllib3.HTTPResponse]`. + +Returns a tuple with 2 values: `({{#returnType}}parsed_response{{/returnType}}{{^returnType}}None{{/returnType}}, raw_response)`. -{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**](../models/{{returnBaseType}}){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}None (empty response body){{/returnType}} +{{#returnType}}The first value is a model parsed from the response data.{{/returnType}}{{^returnType}}This endpoint does not have any return value, so `None` is always returned as the first value.{{/returnType}} +The second value is the raw response, which can be useful to get response parameters, such as +status code, headers, or raw response data. Read more about invocation parameters +and returned values [here](https://opencv.github.io/cvat/docs/api_sdk/sdk/lowlevel-api/#sending-requests). ### Authorization @@ -51,7 +67,7 @@ Name | Type | Description | Notes | Status code | Description | Response headers | |-------------|-------------|------------------| {{#responses}} -**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{{description}}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | {{/responses}} {{/responses.0}} diff --git a/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache b/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache index d671039b3..4aeaf9b01 100644 --- a/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache +++ b/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache @@ -1,11 +1,9 @@ ```python -import time -from {{{packageName}}} import Configuration, ApiClient, exceptions -{{#imports}} -{{.}} -{{/imports}} from pprint import pprint +from {{{packageName}}} import Configuration, ApiClient, exceptions +from {{{packageName}}}.models import * + # Set up an API client # Read Configuration class docs for more info about parameters and authentication methods configuration = Configuration( @@ -32,7 +30,7 @@ with ApiClient(configuration) as api_client: {{/optionalParams}} try: - {{#returnType}}(data, response) = {{/returnType}}%%%make_api_name!!!{{classname}}%%%.{{{operationId}}}({{#requiredParams}} + {{#returnType}}(data, response) = {{/returnType}}api_client.%%%make_api_name!!!{{classname}}%%%.{{>operation_name}}({{#requiredParams}} {{^defaultValue}}{{paramName}},{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} {{paramName}}={{paramName}},{{#-last}} {{/-last}}{{/optionalParams}}) @@ -40,5 +38,5 @@ with ApiClient(configuration) as api_client: pprint(data) {{/returnType}} except exceptions.ApiException as e: - print("Exception when calling {{classname}}.{{operationId}}: %s\n" % e) + print("Exception when calling {{classname}}.{{>operation_name}}(): %s\n" % e) ``` diff --git a/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache b/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache index 94d13c14e..013b198c5 100644 --- a/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache +++ b/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache @@ -1,32 +1,32 @@ {{#models}}{{#model}}# {{classname}} -{{#description}}{{&description}} +{{#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}} +**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}} +**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}} +**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}} +**{{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}} +**{{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}} +**{{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}} diff --git a/site/content/en/docs/api_sdk/sdk/lowlevel-api.md b/site/content/en/docs/api_sdk/sdk/lowlevel-api.md index 49be93022..6e4928c02 100644 --- a/site/content/en/docs/api_sdk/sdk/lowlevel-api.md +++ b/site/content/en/docs/api_sdk/sdk/lowlevel-api.md @@ -292,7 +292,18 @@ class. You can find summary about available API classes and supported endpoints server endpoints. By default, all operations return 2 objects: the parsed response data and the response itself. -A typical call looks like this: + +The first returned value is a model parsed from the response data. If a method does +not have any return value, `None` is always returned as the first value. You can control +automatic parsing using the `_parse_response` method kwarg. When disabled, `None` is returned. + +The second value is the raw response, which can be useful to get response parameters, such as +status code, headers, or raw response data. By default, the status code of the response is +checked to be positive. In the case of request failure, an exception is raised by default. +This behavior can be controlled by the `_check_status` method kwarg. If the status is not +checked, you will need to manually check the response status code and perform actions needed. + +A typical endpoint call looks like this: ```python from cvat_sdk.api_client import ApiClient, apis @@ -303,20 +314,28 @@ with ApiClient(...) as api_client: # process the response ... ``` -Operation parameters can be passed as positional or keyword arguments. There are also several -extra arguments which change invocation logic: +Operation parameters can be passed as positional or keyword arguments. API methods provide +extra common arguments which control invocation logic: -- `_parse_response` - Allows to enable and disable response data parsing. When enabled, +- `_parse_response` (`bool`) - 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 + Default is `True`. +- `_check_status` (`bool`) - 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. + In the case of negative status, an exception is raised. Default is `True`. +- `_validate_inputs` (`bool`): specifies if type checking should be done on the data + sent to the server. Default is `True`. +- `_validate_outputs` (`bool`): specifies if type checking should be done on the data + received from the server. Default is `True`. +- `_request_timeout` (`None | int | float | Tuple[int | float, int | float]`) - + Allows to control timeouts. If one number is provided, it will be the total request timeout. It can also + be a tuple with (connection, read) timeouts. Default is `None`, which means no timeout. +- `_content_type` (`None | str`) - 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. + Read more about file uploads [here](#sending-data). Default is `application/json`. > **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 @@ -363,3 +382,104 @@ project_tasks = get_paginated_collection( id=project_id, ) ``` + +### Binary data in requests and responses + +At the moment, sending and receiving binary data - such as files - can be difficult via the +low-level SDK API. Please use the following recommendations. + +#### Sending data + +By default, requests use the `application/json` content type, which is a text type. +However, it's inefficient to send binary data in this encoding, and the data passed +won't be converted automatically. If you need to send files or other binary data, +please specify `_content_type="multipart/form-data"` in the request parameters: + +Example: + +```python +(_, response) = api_client.tasks_api.create_data( + id=42, + data_request=models.DataRequest( + client_files=[ + open("image.jpg", 'rb') + ], + image_quality=70, + ), + _content_type="multipart/form-data", # required +) +``` + +Please also note that if there are complex fields in the data (such as nested lists or dicts), +they, in turn, cannot be encoded as `multipart/form-data`, so the recommended solution is to +split fields into files and others, and send them in different requests with different content +types: + +Example: + +```python +data = { + 'client_files': [...], # a list of binary files + 'image_quality': ..., # a simple type - int + 'job_file_mapping': [...], # a complex type - list +} + +# Initialize uploading +api_client.tasks_api.create_data( + id=42, + data_request=models.DataRequest(image_quality=data["image_quality"]), + upload_start=True, +) + +# Upload binary data +api_client.tasks_api.create_data( + id=42, + data_request=models.DataRequest( + client_files=data.pop("client_files"), + image_quality=data["image_quality"], + ), + upload_multiple=True, + _content_type="multipart/form-data", +) + +# Finalize the uploading and send the remaining fields +api_client.tasks_api.create_data( + id=42, + data_request=models.DataRequest(**data), + upload_finish=True, +) +``` + +#### Receiving data + +Receiving binary files can also be difficult with the low-level API. To avoid unexpected +behavior, it is recommended to specify `_parse_response=False` in the request parameters. +In this case, SDK will not try to parse models from responses, and the response data +can be fetched directly from the response: + +```python +from time import sleep + +# Export a task as a dataset +while True: + (_, response) = api_client.tasks_api.retrieve_dataset( + id=42, + format='COCO 1.0', + _parse_response=False, + ) + if response.status == HTTPStatus.CREATED: + break + + sleep(interval) + +(_, response) = api_client.tasks_api.retrieve_dataset( + id=42, + format='COCO 1.0', + action="download", + _parse_response=False, +) + +# Save the resulting file +with open('output_file', 'wb') as output_file: + output_file.write(response.data) +``` -- GitLab