diff --git a/cvat-sdk/cvat_sdk/core/client.py b/cvat-sdk/cvat_sdk/core/client.py index a60ec637..f4e5d84e 100644 --- a/cvat-sdk/cvat_sdk/core/client.py +++ b/cvat-sdk/cvat_sdk/core/client.py @@ -30,6 +30,10 @@ from cvat_sdk.version import VERSION @attrs.define class Config: + """ + Allows to tweak behavior of Client instances. + """ + status_check_period: float = 5 """Operation status check period, in seconds""" @@ -42,7 +46,8 @@ class Config: class Client: """ - Manages session and configuration. + Provides session management, implements authentication operations + and simplifies access to server APIs. """ SUPPORTED_SERVER_VERSIONS = ( @@ -61,17 +66,26 @@ class Client: check_server_version: bool = True, ) -> None: url = self._validate_and_prepare_url(url) + self.logger = logger or logging.getLogger(__name__) + """The root logger""" + self.config = config or Config() + """Configuration for this object""" + self.api_map = CVAT_API_V2(url) + """Handles server API URL interaction logic""" + self.api_client = ApiClient( Configuration(host=self.api_map.host, verify_ssl=self.config.verify_ssl) ) + """Provides low-level access to the CVAT server""" if check_server_version: self.check_server_version() self._repos: Dict[str, Repo] = {} + """A cache for created Repository instances""" ALLOWED_SCHEMAS = ("https", "http") diff --git a/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py b/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py index df8b86a9..9f71fdd9 100644 --- a/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py +++ b/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py @@ -55,7 +55,8 @@ class ModelProxy(ABC, Generic[ModelType, ApiType]): class Entity(ModelProxy[ModelType, ApiType]): """ - Represents a single object. Implements related operations and provides access to data members. + Represents a single object. Implements related operations and provides read access + to data members. """ _model: ModelType @@ -121,7 +122,7 @@ _EntityT = TypeVar("_EntityT", bound=Entity) class ModelCreateMixin(Generic[_EntityT, IModel]): def create(self: Repo, spec: Union[Dict[str, Any], IModel]) -> _EntityT: """ - Creates a new object on the server and returns corresponding local object + Creates a new object on the server and returns the corresponding local object """ (model, _) = self.api.create(spec) @@ -131,7 +132,7 @@ class ModelCreateMixin(Generic[_EntityT, IModel]): class ModelRetrieveMixin(Generic[_EntityT]): def retrieve(self: Repo, obj_id: int) -> _EntityT: """ - Retrieves an object from server by ID + Retrieves an object from the server by ID """ (model, _) = self.api.retrieve(id=obj_id) @@ -181,7 +182,7 @@ class ModelUpdateMixin(ABC, Generic[IModel]): def fetch(self: Entity) -> Self: """ - Updates current object from the server + Updates the current object from the server """ # TODO: implement revision checking @@ -190,7 +191,9 @@ class ModelUpdateMixin(ABC, Generic[IModel]): def update(self: Entity, values: Union[Dict[str, Any], IModel]) -> Self: """ - Commits local model changes to the server + Commits model changes to the server + + The local object is updated from the server after this operation. """ # TODO: implement revision checking diff --git a/site/content/en/docs/api_sdk/sdk/_index.md b/site/content/en/docs/api_sdk/sdk/_index.md index 75d9eae0..870ad147 100644 --- a/site/content/en/docs/api_sdk/sdk/_index.md +++ b/site/content/en/docs/api_sdk/sdk/_index.md @@ -1,5 +1,5 @@ --- -title: 'Python SDK' +title: 'CVAT Python SDK' linkTitle: 'SDK' weight: 3 description: '' @@ -7,17 +7,18 @@ description: '' ## Overview -SDK is a Python library. It provides you access to Python function and objects, which -simplify server interaction and provide additional functionality like data validation. +CVAT SDK is a Python library. It provides you access to Python functions and objects that +simplify server interaction and provide additional functionality like data validation +and serialization. SDK API includes 2 layers: -- 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) +- Low-level API with REST API wrappers. Located at `cvat_sdk.api_client`. [Read more](/docs/api_sdk/sdk/lowlevel-api) +- High-level API. Located at `cvat_sdk.core`. [Read more](/docs/api_sdk/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. -For most uses, the high-level API should be good enough and sufficient, and it should be -right point to start your integration with CVAT. +In general, the low-level API provides single-request operations, while the high-level one +implements composite, multi-request operations, and provides local proxies for server objects. +For most uses, the high-level API should be good enough, and it should be +the right point to start your integration with CVAT. ## Installation @@ -30,9 +31,9 @@ We support Python versions 3.7 - 3.9. ## Usage -To import the package components, use the following code: +To import package components, use the following code: -For high-level API: +For the high-level API: ```python import cvat_sdk @@ -40,7 +41,7 @@ import cvat_sdk import cvat_sdk.core ``` -For low-level API: +For the low-level API: ```python import cvat_sdk.api_client diff --git a/site/content/en/docs/api_sdk/sdk/highlevel-api.md b/site/content/en/docs/api_sdk/sdk/highlevel-api.md index a86e4a08..e5391ff3 100644 --- a/site/content/en/docs/api_sdk/sdk/highlevel-api.md +++ b/site/content/en/docs/api_sdk/sdk/highlevel-api.md @@ -9,10 +9,13 @@ description: '' This layer provides high-level APIs, allowing easier access to server operations. API includes _Repositories_ and _Entities_. Repositories provide management -operations for Entitites. Entitites represent separate objects on the server -(e.g. tasks, jobs etc). +operations for Entities. Entities represent objects on the server +(e.g. projects, tasks, jobs etc) and simplify interaction with them. The key difference +from the low-level API is that operations on this layer are not limited by a single +server request per operation and encapsulate low-level request machinery behind a high-level +object-oriented API. -Code of this component is located in `cvat_sdk.core`. +The code of this component is located in the `cvat_sdk.core` package. ## Example @@ -20,13 +23,14 @@ Code of this component is located in `cvat_sdk.core`. from cvat_sdk import make_client, models from cvat_sdk.core.proxies.tasks import ResourceType, Task -with make_client(host="http://localhost") as client: - # Authorize using the basic auth - client.login(('YOUR_USERNAME', 'YOUR_PASSWORD')) +# Create a Client instance bound to a local server and authenticate using basic auth +with make_client(host="localhost", credentials=('user', 'password')) as client: + # Let's create a new task. - # Models are used the same way as in the layer 1 + # Fill in task parameters first. + # Models are used the same way as in the layer 1. task_spec = { - "name": "example task 2", + "name": "example task", "labels": [ { "name": "car", @@ -44,20 +48,174 @@ with make_client(host="http://localhost") as client: ], } - # Different repositories can be accessed as the Client class members. - # They may provide both simple and complex operations, - # such as entity creation, retrieval and removal. + # Now we can create a task using a task repository method. + # Repositories can be accessed as the Client class members. + # In this case we use 2 local images as the task data. task = client.tasks.create_from_data( spec=task_spec, resource_type=ResourceType.LOCAL, resources=['image1.jpg', 'image2.png'], ) - # Task object is already up-to-date with its server counterpart + # The returned task object is already up-to-date with its server counterpart. + # Now we can access task fields. The fields are read-only and can be optional. + # Let's check that we have 2 images in the task data. assert task.size == 2 - # An entity needs to be fetch()-ed to reflect the latest changes. - # It can be update()-d and remove()-d depending on the entity type. - task.update({'name': 'mytask'}) - task.remove() + # If an object is modified on the server, the local object is not updated automatically. + # To reflect the latest changes, the local object needs to be fetch()-ed. + task.fetch() + + # Let's obtain another task. Again, it can be done via the task repository. + # Suppose we have already created the task earlier and know the task id. + task2 = client.tasks.retrieve(42) + + # The task object fields can be update()-d. Note that the set of fields that can be + # modified can be different from what is available for reading. + task2.update({'name': 'my task'}) + + # And the task can also be remove()-d from the server. The local copy will remain + # untouched. + task2.remove() ``` + +## Client + +The `cvat_sdk.core.client.Client` class provides session management, implements +authentication operations and simplifies access to server APIs. +It is the starting point for using CVAT SDK. + +A `Client` instance allows you to: +- configure connection options with the `Config` class +- check server API compatibility with the current SDK version +- deduce server connection scheme (`https` or `http`) automatically +- manage user session with the `login()`, `logout()` and other methods +- obtain Repository objects with the `users`, `tasks`, `jobs` and other members +- reach to lower-level APIs with the corresponding members + +An instance of `Client` can be created directly by calling the class constructor +or with the utility function `cvat_sdk.core.client.make_client()` which can handle +some configuration for you. A `Client` can be configured with +the `cvat_sdk.core.client.Config` class instance. A `Config` object can be passed to +the `Client` constructor and then it will be available in the `Client.config` field. + +The `Client` class implements the [context manager protocol](https://docs.python.org/3/reference/datamodel.html#context-managers). +When the context is closed, the session is finished, and the user is logged out +automatically. Otherwise, these actions can be done with the `close()` and `logout()` methods. + +You can create and start using a `Client` instance this way: + +```python +from cvat_sdk import make_client + +with make_client('localhost', port='8080', credentials=('user', 'password')) as client: + ... +``` + +The `make_client()` function handles configuration and object creation for you. +It also allows to authenticate right after the object is created. + +If you need to configure `Client` parameters, you can do this: + +```python +from cvat_sdk import Config, Client + +config = Config() +# set up some config fields ... + +with Client('localhost:8080', config=config) as client: + client.login(('user', 'password')) + ... +``` + +You can specify server address both with and without the scheme. If the scheme is omitted, +it will be deduced automatically. + +> The checks are performed in the following +order: `https` (with the default port 8080), `http` (with the default port 80). +In some cases it may lead to incorrect results - e.g. you have 2 servers running on the +same host at default ports. In such cases just specify the schema manually: `https://localhost`. + +When the server is located, its version is checked. If an unsupported version is found, +an error can be raised or suppressed (controlled by `config.allow_unsupported_server`). +If the error is suppressed, some SDK functions may not work as expected with this server. +By default, a warning is raised and the error is suppressed. + +> Please note that all `Client` operations rely on the server API and depend on the current user +rights. This affects the set of available APIs, objects and actions. For example, a regular user +can only see and modify their tasks and jobs, while an admin user can see all the tasks etc. + +## Entities and Repositories + +_Entities_ represent objects on the server. They provide read access to object fields +and implement additional relevant operations, including both the general Read-Update-Delete and +object-specific ones. The set of available general operations depends on the object type. + +_Repositories_ provide management operations for corresponding Entities. You don't +need to create Repository objects manually. To obtain a Repository object, use the +corresponding `Client` instance member: + +```python +client.projects +client.tasks +client.jobs +client.users +... +``` + +An Entity can be created on the server with the corresponding Repository method `create()`: + +```python +task = client.tasks.create() +``` + +We can retrieve server objects using the `retrieve()` and `list()` methods of the Repository: + +```python +job = client.jobs.retrieve() +tasks = client.tasks.list() +``` + +After calling these functions, we obtain local objects representing their server counterparts. + +Object fields can be updated with the `update()` method. Note that the set of fields that can be +modified can be different from what is available for reading. + +```python +job.update({'stage': 'validation'}) +``` + +The server object will be updated and the local object will reflect the latest object state +after calling this operation. + +Note that local objects may fall out of sync with their server counterparts for different reasons. +If you need to update the local object with the latest server state, use the `fetch()` method: + +```python +# obtain 2 local copies of the same job +job_ref1 = client.jobs.retrieve(1) +job_ref2 = client.jobs.retrieve(1) + +# update the server object with the first reference +job_ref1.update(...) +# job_ref2 is outdated now + +job_ref2.fetch() +# job_ref2 is synced +``` + +Finally, if you need to remove the object from the server, you can use the `remove()` method. +The server object will be removed, but the local copy of the object will remain untouched. + +```python +task = client.tasks.retrieve() +task.remove() +``` + +Repositories can also provide group operations over entities. For instance, you can retrieve +all available objects using the `list()` Repository method. The list of available +Entity and Repository operations depends on the object type. + +You can learn more about entity members and how model parameters are passed to functions [here](../lowlevel-api). + +The implementation for these components is located in `cvat_sdk.core.proxies`.