superannotate 4.6.0__tar.gz → 4.6.1.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {superannotate-4.6.0/src/superannotate.egg-info → superannotate-4.6.1.dev1}/PKG-INFO +1 -1
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/__init__.py +5 -1
- superannotate-4.6.1.dev1/src/superannotate/lib/app/interface/base_interface.py +395 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/sdk_interface.py +113 -4
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/__init__.py +18 -5
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/__init__.py +6 -1
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/base.py +6 -2
- superannotate-4.6.1.dev1/src/superannotate/lib/core/entities/context.py +106 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/project.py +14 -1
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/work_managament.py +1 -1
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/exceptions.py +12 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/service_types.py +4 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/serviceproviders.py +33 -16
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/annotations.py +72 -53
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/images.py +12 -8
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/items.py +10 -7
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/projects.py +23 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/annotation_adapter.py +3 -3
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/controller.py +112 -75
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/serviceprovider.py +19 -10
- superannotate-4.6.1.dev1/src/superannotate/lib/infrastructure/services/auth.py +144 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/http_client.py +34 -19
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/work_management.py +43 -36
- {superannotate-4.6.0 → superannotate-4.6.1.dev1/src/superannotate.egg-info}/PKG-INFO +1 -1
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate.egg-info/SOURCES.txt +1 -0
- superannotate-4.6.0/src/superannotate/lib/app/interface/base_interface.py +0 -256
- superannotate-4.6.0/src/superannotate/lib/infrastructure/services/auth.py +0 -139
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/LICENSE +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/MANIFEST.in +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/README.rst +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/requirements.txt +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/setup.cfg +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/setup.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/analytics/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/analytics/aggregators.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/analytics/common.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/bin/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/bin/superannotate.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/common.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/exceptions.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/helpers.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/conversion.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/baseStrategy.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/coco_api.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/coco_converter.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/coco_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/coco_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/coco_converters/sa_vector_to_coco.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/converters.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/dataloop_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/dataloop_converters/dataloop_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/dataloop_converters/dataloop_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/dataloop_converters/dataloop_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/googlecloud_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/googlecloud_converters/googlecloud_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/googlecloud_converters/googlecloud_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/labelbox_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/labelbox_converters/labelbox_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/labelbox_converters/labelbox_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/sa_json_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/sagemaker_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/sagemaker_converters/sagemaker_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/sagemaker_converters/sagemaker_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/supervisely_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/supervisely_converters/supervisely_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/supervisely_converters/supervisely_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/supervisely_converters/supervisely_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vgg_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vgg_converters/vgg_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vgg_converters/vgg_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vgg_converters/vgg_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/voc_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/voc_converters/voc_helper.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/voc_converters/voc_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/voc_converters/voc_to_sa_pixel.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/voc_converters/voc_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vott_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vott_converters/vott_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/vott_converters/vott_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/yolo_converters/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/yolo_converters/yolo_strategies.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/converters/yolo_converters/yolo_to_sa_vector.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/export_from_sa_conversions.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/input_converters/import_to_sa_conversions.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/cli_interface.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/responses.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/sdk/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/sdk/folders.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/sdk/project.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/interface/types.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/app/serializers.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/base_usecases.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/conditions.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/config.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/classes.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/filters.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/folder.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/integrations.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/items.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/multimodal_form.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/project_entities.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/enums.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/jsx_conditions.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/plugin.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/reporter.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/repositories.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/response.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/types.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/base.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/classes.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/custom_fields.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/folders.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/integrations.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/models.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/usecases/work_management.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/utils.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/video_convertor.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/custom_entities.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/helpers.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/query_builder.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/repositories.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/__init__.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/annotation.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/annotation_class.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/explore.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/folder.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/integration.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/item.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/item_service.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/project.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/services/telemetry_scoring.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/stream_data_handler.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/utils.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/infrastructure/validators.py +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate.egg-info/dependency_links.txt +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate.egg-info/entry_points.txt +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate.egg-info/requires.txt +0 -0
- {superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate.egg-info/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
|
-
__version__ = "4.6.
|
|
5
|
+
__version__ = "4.6.1dev1"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
os.environ.update({"sa_version": __version__})
|
|
@@ -15,10 +15,12 @@ from lib.core import PACKAGE_VERSION_UPGRADE
|
|
|
15
15
|
from lib.core import PACKAGE_VERSION_INFO_MESSAGE
|
|
16
16
|
from lib.core import PACKAGE_VERSION_MAJOR_UPGRADE
|
|
17
17
|
from lib.core.exceptions import AppException
|
|
18
|
+
from lib.core.exceptions import SAAuthError
|
|
18
19
|
from lib.core.exceptions import FileChangedError
|
|
19
20
|
from superannotate.lib.app.input_converters import export_annotation
|
|
20
21
|
from superannotate.lib.app.input_converters import import_annotation
|
|
21
22
|
from superannotate.lib.app.interface.sdk_interface import SAClient
|
|
23
|
+
from superannotate.lib.app.interface.sdk_interface import SAORGClient
|
|
22
24
|
from superannotate.lib.app.interface.sdk_interface import ItemContext
|
|
23
25
|
|
|
24
26
|
SESSIONS = {}
|
|
@@ -27,10 +29,12 @@ SESSIONS = {}
|
|
|
27
29
|
__all__ = [
|
|
28
30
|
"__version__",
|
|
29
31
|
"SAClient",
|
|
32
|
+
"SAORGClient",
|
|
30
33
|
"ItemContext",
|
|
31
34
|
# Utils
|
|
32
35
|
"enums",
|
|
33
36
|
"AppException",
|
|
37
|
+
"SAAuthError",
|
|
34
38
|
"FileChangedError",
|
|
35
39
|
"import_annotation",
|
|
36
40
|
"export_annotation",
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from collections.abc import Sized
|
|
11
|
+
from inspect import signature
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import FunctionType
|
|
14
|
+
|
|
15
|
+
import lib.core as constants
|
|
16
|
+
from lib.app.interface.types import validate_arguments
|
|
17
|
+
from lib.core import CONFIG
|
|
18
|
+
from lib.core import CREDENTIALS_NOT_FOUND_ERROR
|
|
19
|
+
from lib.core import INVALID_CREDENTIALS_ERROR
|
|
20
|
+
from lib.core import INVALID_TOKEN_ERROR
|
|
21
|
+
from lib.core import setup_logging
|
|
22
|
+
from lib.core.entities.base import ConfigEntity
|
|
23
|
+
from lib.core.exceptions import AppException
|
|
24
|
+
from lib.core.exceptions import SAAuthError
|
|
25
|
+
from lib.infrastructure.controller import BaseController
|
|
26
|
+
from lib.infrastructure.utils import extract_project_folder_inputs
|
|
27
|
+
from lib.infrastructure.validators import wrap_error
|
|
28
|
+
from mixpanel import Mixpanel
|
|
29
|
+
from pydantic import ValidationError
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("sa")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseInterfaceFacade:
|
|
35
|
+
"""Credential resolution, shared by every facade.
|
|
36
|
+
|
|
37
|
+
What a token has to resolve to is the facade's own business - named by its
|
|
38
|
+
CONTROLLER_CLASS rather than configured through this __init__: SAClient needs a
|
|
39
|
+
team, SAORGClient needs an organization key.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
#: The controller class this facade drives. Named for the class, not the
|
|
43
|
+
#: instance: `self.controller` is the built one.
|
|
44
|
+
CONTROLLER_CLASS: type[BaseController]
|
|
45
|
+
|
|
46
|
+
@validate_arguments
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
# Plain str, not TokenStr: the token's shape is checked by ConfigEntity in
|
|
50
|
+
# _resolve_config, so a malformed one is reported as the credential failure it
|
|
51
|
+
# is (SAAuthError) rather than as a generic bad argument.
|
|
52
|
+
token: str | None = None,
|
|
53
|
+
config_path: str | None = None,
|
|
54
|
+
team_id: int | None = None,
|
|
55
|
+
*,
|
|
56
|
+
config: dict | None = None,
|
|
57
|
+
):
|
|
58
|
+
resolved = self._resolve_config(token, config_path, config)
|
|
59
|
+
if team_id is not None:
|
|
60
|
+
# An explicit team_id wins over whatever the config source provided.
|
|
61
|
+
resolved.TEAM_ID = team_id
|
|
62
|
+
setup_logging(resolved.LOGGING_LEVEL, resolved.LOGGING_PATH)
|
|
63
|
+
self.controller = self.CONTROLLER_CLASS(resolved)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def _resolve_config(
|
|
67
|
+
cls,
|
|
68
|
+
token: str | None,
|
|
69
|
+
config_path: str | None,
|
|
70
|
+
settings: dict | None = None,
|
|
71
|
+
) -> ConfigEntity:
|
|
72
|
+
"""Resolve credentials and settings.
|
|
73
|
+
|
|
74
|
+
Credentials come from the first source that carries them: the ``token``
|
|
75
|
+
argument, a config file, inline ``settings``, then the environment and the
|
|
76
|
+
default config files. Inline settings are applied over whatever that produced,
|
|
77
|
+
so a caller can configure a client - another backend, bigger chunks - without
|
|
78
|
+
writing a file for it.
|
|
79
|
+
|
|
80
|
+
Shared by every facade's ``__init__`` (``SAClient``, ``SAORGClient``).
|
|
81
|
+
"""
|
|
82
|
+
settings = cls._validated_settings(settings)
|
|
83
|
+
try:
|
|
84
|
+
if token:
|
|
85
|
+
config = ConfigEntity(**{**settings, "SA_TOKEN": token})
|
|
86
|
+
elif config_path:
|
|
87
|
+
config = cls._merge_settings(
|
|
88
|
+
cls._resolve_config_from_path(config_path), settings
|
|
89
|
+
)
|
|
90
|
+
elif "SA_TOKEN" in settings:
|
|
91
|
+
config = ConfigEntity(**settings)
|
|
92
|
+
else:
|
|
93
|
+
config = cls._merge_settings(
|
|
94
|
+
cls._resolve_config_from_env_or_files(), settings
|
|
95
|
+
)
|
|
96
|
+
except ValidationError as e:
|
|
97
|
+
raise SAAuthError(wrap_error(e))
|
|
98
|
+
if not config:
|
|
99
|
+
raise SAAuthError(INVALID_CREDENTIALS_ERROR)
|
|
100
|
+
return config
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _validated_settings(settings: dict | None) -> dict:
|
|
104
|
+
"""Inline settings, with anything the SDK has no such setting for rejected.
|
|
105
|
+
|
|
106
|
+
ConfigEntity ignores what it does not recognise, so a mistyped key would
|
|
107
|
+
otherwise be dropped without a word - and a mistyped SA_TOKEN would send the
|
|
108
|
+
client off to authenticate as whatever the environment happens to hold.
|
|
109
|
+
"""
|
|
110
|
+
if not settings:
|
|
111
|
+
return {}
|
|
112
|
+
known = set(ConfigEntity.model_fields) | {
|
|
113
|
+
field.alias
|
|
114
|
+
for field in ConfigEntity.model_fields.values()
|
|
115
|
+
if field.alias is not None
|
|
116
|
+
}
|
|
117
|
+
unknown = sorted(set(settings) - known)
|
|
118
|
+
if unknown:
|
|
119
|
+
raise AppException(
|
|
120
|
+
f"Unknown configuration: {', '.join(unknown)}. "
|
|
121
|
+
f"Available: {', '.join(sorted(known))}."
|
|
122
|
+
)
|
|
123
|
+
return dict(settings)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _merge_settings(config: ConfigEntity, settings: dict) -> ConfigEntity:
|
|
127
|
+
"""``config`` with inline settings applied over it."""
|
|
128
|
+
if not settings:
|
|
129
|
+
return config
|
|
130
|
+
return ConfigEntity(**{**config.model_dump(by_alias=True), **settings})
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def _resolve_config_from_path(cls, config_path: str) -> ConfigEntity:
|
|
134
|
+
"""A config file the caller named explicitly (``.ini`` or ``.json``)."""
|
|
135
|
+
path = Path(config_path).expanduser()
|
|
136
|
+
if not path.is_file() or not os.access(path, os.R_OK):
|
|
137
|
+
raise AppException(f"SuperAnnotate config file {config_path} not found.")
|
|
138
|
+
if path.suffix == ".json":
|
|
139
|
+
return cls._retrieve_configs_from_json(path)
|
|
140
|
+
return cls._retrieve_configs_from_ini(path)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def _resolve_config_from_env_or_files(cls) -> ConfigEntity:
|
|
144
|
+
"""No token or config path given: ``SA_TOKEN``, then the default config files."""
|
|
145
|
+
config = cls._retrieve_configs_from_env()
|
|
146
|
+
if config:
|
|
147
|
+
return config
|
|
148
|
+
if Path(constants.CONFIG_INI_FILE_LOCATION).exists():
|
|
149
|
+
return cls._retrieve_configs_from_ini(constants.CONFIG_INI_FILE_LOCATION)
|
|
150
|
+
if Path(constants.CONFIG_JSON_FILE_LOCATION).exists():
|
|
151
|
+
return cls._retrieve_configs_from_json(constants.CONFIG_JSON_FILE_LOCATION)
|
|
152
|
+
raise SAAuthError(CREDENTIALS_NOT_FOUND_ERROR)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _retrieve_configs_from_json(path: Path | str) -> ConfigEntity:
|
|
156
|
+
with open(path) as json_file:
|
|
157
|
+
json_data = json.load(json_file)
|
|
158
|
+
token = json_data["token"]
|
|
159
|
+
try:
|
|
160
|
+
config = ConfigEntity(SA_TOKEN=token)
|
|
161
|
+
except ValidationError:
|
|
162
|
+
raise SAAuthError(INVALID_TOKEN_ERROR)
|
|
163
|
+
host = json_data.get("main_endpoint")
|
|
164
|
+
verify_ssl = json_data.get("ssl_verify")
|
|
165
|
+
team_id = json_data.get("team_id")
|
|
166
|
+
if team_id is not None:
|
|
167
|
+
config.TEAM_ID = int(team_id)
|
|
168
|
+
if host:
|
|
169
|
+
config.API_URL = host
|
|
170
|
+
if verify_ssl:
|
|
171
|
+
config.VERIFY_SSL = verify_ssl
|
|
172
|
+
return config
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _retrieve_configs_from_ini(path: Path | str) -> ConfigEntity:
|
|
176
|
+
import configparser
|
|
177
|
+
|
|
178
|
+
config_parser = configparser.ConfigParser()
|
|
179
|
+
config_parser.optionxform = str
|
|
180
|
+
config_parser.read(path)
|
|
181
|
+
config_data = {}
|
|
182
|
+
for key in config_parser["DEFAULT"]:
|
|
183
|
+
config_data[key.upper()] = config_parser["DEFAULT"][key]
|
|
184
|
+
return ConfigEntity(**config_data)
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _retrieve_configs_from_env() -> ConfigEntity | None:
|
|
188
|
+
token = os.environ.get("SA_TOKEN")
|
|
189
|
+
if not token:
|
|
190
|
+
return None
|
|
191
|
+
config = ConfigEntity(**dict(os.environ))
|
|
192
|
+
host = os.environ.get("SA_URL")
|
|
193
|
+
verify_ssl = not os.environ.get("SA_SSL", "True").lower() in ("false", "f", "0")
|
|
194
|
+
if host:
|
|
195
|
+
config.API_URL = host
|
|
196
|
+
if verify_ssl:
|
|
197
|
+
config.VERIFY_SSL = verify_ssl
|
|
198
|
+
return config
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class Tracker:
|
|
202
|
+
def get_mp_instance(self, client, explicit_credentials: bool = False) -> Mixpanel:
|
|
203
|
+
# client may have no .controller yet (e.g. __init__ failed before setting one).
|
|
204
|
+
controller = getattr(client, "controller", None)
|
|
205
|
+
if controller is not None:
|
|
206
|
+
api_url = controller.config["SA_URL"]
|
|
207
|
+
elif explicit_credentials:
|
|
208
|
+
api_url = constants.BACKEND_URL
|
|
209
|
+
else:
|
|
210
|
+
api_url = os.environ.get("SA_URL", constants.BACKEND_URL)
|
|
211
|
+
if api_url == constants.BACKEND_URL:
|
|
212
|
+
mp_token = "ca95ed96f80e8ec3be791e2d3097cf51"
|
|
213
|
+
else:
|
|
214
|
+
mp_token = "e741d4863e7e05b1a45833d01865ef0d"
|
|
215
|
+
return Mixpanel(mp_token)
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def get_default_payload(team_name, user_email, auth_type) -> dict:
|
|
219
|
+
"""Built fresh per event: a cached dict would be shared between callers, and
|
|
220
|
+
would freeze sa_version and SA_ENV as they were on the first tracked call.
|
|
221
|
+
"""
|
|
222
|
+
return {
|
|
223
|
+
"SDK": True,
|
|
224
|
+
"Team": team_name,
|
|
225
|
+
"User Email": user_email,
|
|
226
|
+
"Auth Type": auth_type,
|
|
227
|
+
"Version": os.environ["sa_version"],
|
|
228
|
+
"Python version": platform.python_version(),
|
|
229
|
+
"Python interpreter type": platform.python_implementation(),
|
|
230
|
+
"Env": os.environ.get("SA_ENV", "N/A"),
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def __init__(self, function):
|
|
234
|
+
self.function = function
|
|
235
|
+
functools.update_wrapper(self, function)
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _metrics_disabled() -> bool:
|
|
239
|
+
"""Whether the caller turned telemetry off.
|
|
240
|
+
|
|
241
|
+
Read per call: a Tracker is built while its class is being created, so a value
|
|
242
|
+
captured in __init__ would ignore anything set after ``import superannotate``.
|
|
243
|
+
"""
|
|
244
|
+
return os.environ.get("SA_SKIP_METRICS", "False").lower() in ("true", "1", "t")
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def extract_arguments(function, *args, **kwargs) -> dict:
|
|
248
|
+
bound_arguments = signature(function).bind(*args, **kwargs)
|
|
249
|
+
bound_arguments.apply_defaults()
|
|
250
|
+
return dict(bound_arguments.arguments)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def default_parser(function_name: str, kwargs: dict) -> tuple:
|
|
254
|
+
properties = {}
|
|
255
|
+
for key, value in kwargs.items():
|
|
256
|
+
if key == "self":
|
|
257
|
+
continue
|
|
258
|
+
if key == "token":
|
|
259
|
+
properties["sa_token"] = str(bool(value))
|
|
260
|
+
elif key == "config_path":
|
|
261
|
+
properties[key] = str(bool(value))
|
|
262
|
+
elif value is None:
|
|
263
|
+
properties[key] = value
|
|
264
|
+
elif key == "project":
|
|
265
|
+
properties.update(extract_project_folder_inputs(value))
|
|
266
|
+
elif isinstance(value, (str, int, float, bool)):
|
|
267
|
+
properties[key] = value
|
|
268
|
+
elif isinstance(value, dict):
|
|
269
|
+
properties[key] = list(value.keys())
|
|
270
|
+
elif isinstance(value, Sized):
|
|
271
|
+
properties[key] = len(value)
|
|
272
|
+
elif isinstance(value, Iterable):
|
|
273
|
+
properties[key] = "N/A"
|
|
274
|
+
else:
|
|
275
|
+
properties[key] = str(value)
|
|
276
|
+
return function_name, properties
|
|
277
|
+
|
|
278
|
+
def _track(
|
|
279
|
+
self,
|
|
280
|
+
user_id: str,
|
|
281
|
+
event_name: str,
|
|
282
|
+
data: dict,
|
|
283
|
+
*,
|
|
284
|
+
client,
|
|
285
|
+
explicit_credentials: bool = False,
|
|
286
|
+
):
|
|
287
|
+
if "pytest" in sys.modules:
|
|
288
|
+
return
|
|
289
|
+
self.get_mp_instance(client, explicit_credentials).track(
|
|
290
|
+
user_id, event_name, data
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def _failure_reason(
|
|
295
|
+
cls, function_name: str, success: bool, error: BaseException | None
|
|
296
|
+
):
|
|
297
|
+
"""The original error message, for the "Auth Failure" event property.
|
|
298
|
+
|
|
299
|
+
Scoped to __init__ auth/credential failures only.
|
|
300
|
+
"""
|
|
301
|
+
if success or function_name != "__init__" or error is None:
|
|
302
|
+
return None
|
|
303
|
+
return str(error) if isinstance(error, SAAuthError) else None
|
|
304
|
+
|
|
305
|
+
def _track_method(
|
|
306
|
+
self,
|
|
307
|
+
instance,
|
|
308
|
+
args,
|
|
309
|
+
kwargs,
|
|
310
|
+
success: bool,
|
|
311
|
+
error: BaseException | None = None,
|
|
312
|
+
):
|
|
313
|
+
# Before anything is gathered: building the payload reads controller.team_name,
|
|
314
|
+
# which fetches the team from the backend. A caller who turned metrics off
|
|
315
|
+
# should not pay for a request that is never sent.
|
|
316
|
+
if self._metrics_disabled():
|
|
317
|
+
return
|
|
318
|
+
try:
|
|
319
|
+
function_name = self.function.__name__ if self.function else ""
|
|
320
|
+
arguments = self.extract_arguments(self.function, *args, **kwargs)
|
|
321
|
+
event_name, properties = self.default_parser(function_name, arguments)
|
|
322
|
+
|
|
323
|
+
# instance is args[0] - the actual object the call was made on, captured
|
|
324
|
+
# locally per call (see __call__), not shared/mutable state. It has no
|
|
325
|
+
# .controller yet when __init__ fails before setting one.
|
|
326
|
+
controller = getattr(instance, "controller", None)
|
|
327
|
+
user_email = team_name = auth_type = None
|
|
328
|
+
if controller is not None:
|
|
329
|
+
user_email = controller.current_user.email
|
|
330
|
+
auth_type = controller.token_context.scope.label
|
|
331
|
+
team_name = controller.team_name
|
|
332
|
+
elif instance is None:
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
properties["Success"] = success
|
|
336
|
+
properties["Class"] = instance.__class__.__name__
|
|
337
|
+
if error:
|
|
338
|
+
properties["Failure Reason"] = self._failure_reason(
|
|
339
|
+
function_name, success, error
|
|
340
|
+
)
|
|
341
|
+
default = self.get_default_payload(
|
|
342
|
+
team_name=team_name, user_email=user_email, auth_type=auth_type
|
|
343
|
+
)
|
|
344
|
+
self._track(
|
|
345
|
+
user_email or "",
|
|
346
|
+
event_name,
|
|
347
|
+
{**default, **properties, **CONFIG.get_current_session().data},
|
|
348
|
+
client=instance,
|
|
349
|
+
explicit_credentials=bool(
|
|
350
|
+
arguments.get("token") or arguments.get("config_path")
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
except BaseException:
|
|
354
|
+
logger.debug("Skipped telemetry for this call.", exc_info=True)
|
|
355
|
+
|
|
356
|
+
def __get__(self, obj, owner=None):
|
|
357
|
+
if obj is not None:
|
|
358
|
+
tmp = functools.partial(self.__call__, obj)
|
|
359
|
+
functools.update_wrapper(tmp, self.function)
|
|
360
|
+
return tmp
|
|
361
|
+
return self
|
|
362
|
+
|
|
363
|
+
def __call__(self, *args, **kwargs):
|
|
364
|
+
success = True
|
|
365
|
+
error = None
|
|
366
|
+
# The instance the call is bound to (set by __get__ via functools.partial) -
|
|
367
|
+
# captured locally here, per call, rather than read back from shared state.
|
|
368
|
+
instance = args[0] if args else None
|
|
369
|
+
try:
|
|
370
|
+
result = self.function(*args, **kwargs)
|
|
371
|
+
except BaseException as e:
|
|
372
|
+
# BaseException, not Exception: a KeyboardInterrupt used to skip this and
|
|
373
|
+
# leave the call reported as a success.
|
|
374
|
+
success = False
|
|
375
|
+
error = e
|
|
376
|
+
raise
|
|
377
|
+
else:
|
|
378
|
+
return result
|
|
379
|
+
finally:
|
|
380
|
+
self._track_method(
|
|
381
|
+
instance, args=args, kwargs=kwargs, success=success, error=error
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class TrackableMeta(type):
|
|
386
|
+
def __new__(mcs, name, bases, attrs):
|
|
387
|
+
for attr_name, attr_value in attrs.items():
|
|
388
|
+
if isinstance(
|
|
389
|
+
attr_value, FunctionType
|
|
390
|
+
) and not attr_value.__name__.startswith("_"):
|
|
391
|
+
attrs[attr_name] = Tracker(validate_arguments(attr_value))
|
|
392
|
+
if "__init__" in attrs:
|
|
393
|
+
attrs["__init__"] = Tracker(validate_arguments(attrs["__init__"]))
|
|
394
|
+
tmp = super().__new__(mcs, name, bases, attrs)
|
|
395
|
+
return tmp
|
|
@@ -32,7 +32,8 @@ from pydantic import ValidationError
|
|
|
32
32
|
from pydantic import TypeAdapter
|
|
33
33
|
|
|
34
34
|
import lib.core as constants
|
|
35
|
-
from lib.infrastructure.controller import
|
|
35
|
+
from lib.infrastructure.controller import OrgController
|
|
36
|
+
from lib.infrastructure.controller import TeamController
|
|
36
37
|
from lib.app.helpers import get_annotation_paths
|
|
37
38
|
from lib.app.helpers import get_name_url_duplicated_from_csv
|
|
38
39
|
from lib.app.helpers import wrap_error as wrap_validation_errors
|
|
@@ -67,6 +68,8 @@ from lib.core.enums import CustomFieldEntityEnum
|
|
|
67
68
|
from lib.core.enums import ProjectType
|
|
68
69
|
from lib.core.enums import ClassTypeEnum
|
|
69
70
|
from lib.core.exceptions import AppException
|
|
71
|
+
from lib.core.exceptions import SAAuthError
|
|
72
|
+
from lib.core import INVALID_TEAM_ID_ERROR
|
|
70
73
|
from lib.core.types import PriorityScoreEntity
|
|
71
74
|
from lib.infrastructure.annotation_adapter import BaseMultimodalAnnotationAdapter
|
|
72
75
|
from lib.infrastructure.annotation_adapter import MultimodalSmallAnnotationAdapter
|
|
@@ -141,7 +144,7 @@ class ItemContext:
|
|
|
141
144
|
|
|
142
145
|
def __init__(
|
|
143
146
|
self,
|
|
144
|
-
controller:
|
|
147
|
+
controller: TeamController,
|
|
145
148
|
project: ProjectEntity,
|
|
146
149
|
folder: FolderEntity,
|
|
147
150
|
item: BaseItemEntity,
|
|
@@ -301,10 +304,41 @@ class SAClient(BaseInterfaceFacade, metaclass=TrackableMeta):
|
|
|
301
304
|
:param config_path: path to config file
|
|
302
305
|
:type config_path: path-like (str or Path)
|
|
303
306
|
|
|
307
|
+
:param team_id: the team to operate in. Required for an Organization API key, which
|
|
308
|
+
is not bound to a team; for any other key it is optional and, when given, must
|
|
309
|
+
match the team the key grants access to.
|
|
310
|
+
:type team_id: int
|
|
311
|
+
|
|
312
|
+
:param config: configuration applied on creation, instead of or on top of a config
|
|
313
|
+
file. Keys are a config file's keys - ``SA_TOKEN``, ``SA_URL``, ``SA_TEAM_ID``,
|
|
314
|
+
``VERIFY_SSL``, ``LOGGING_LEVEL``, ``LOGGING_PATH``, ``ANNOTATION_CHUNK_SIZE``,
|
|
315
|
+
``ITEM_CHUNK_SIZE``, ``MAX_THREAD_COUNT``, ``MAX_COROUTINE_COUNT`` - and an
|
|
316
|
+
unrecognised one is an error rather than silently ignored. Explicit ``token``
|
|
317
|
+
and ``team_id`` arguments win over the same keys here.
|
|
318
|
+
:type config: dict
|
|
319
|
+
|
|
320
|
+
Request Example:
|
|
321
|
+
::
|
|
322
|
+
|
|
323
|
+
sa = SAClient(config={"SA_TOKEN": "<API key>", "SA_URL": "<host>"})
|
|
304
324
|
"""
|
|
305
325
|
|
|
306
|
-
|
|
307
|
-
|
|
326
|
+
CONTROLLER_CLASS = TeamController
|
|
327
|
+
|
|
328
|
+
def __init__(
|
|
329
|
+
self,
|
|
330
|
+
token: str | None = None,
|
|
331
|
+
config_path: str | None = None,
|
|
332
|
+
team_id: int | None = None,
|
|
333
|
+
*,
|
|
334
|
+
config: dict | None = None,
|
|
335
|
+
):
|
|
336
|
+
super().__init__(token, config_path, team_id=team_id, config=config)
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def team_id(self) -> int:
|
|
340
|
+
"""The team this client operates in."""
|
|
341
|
+
return self.controller.team_id
|
|
308
342
|
|
|
309
343
|
def get_project_by_id(self, project_id: int):
|
|
310
344
|
"""Returns the project metadata
|
|
@@ -6135,3 +6169,78 @@ class SAClient(BaseInterfaceFacade, metaclass=TrackableMeta):
|
|
|
6135
6169
|
logger.info(
|
|
6136
6170
|
f"Successfully removed {success} users(s) out of the {len(users)} provided from the project {project.name}."
|
|
6137
6171
|
)
|
|
6172
|
+
|
|
6173
|
+
|
|
6174
|
+
class SAORGClient(BaseInterfaceFacade, metaclass=TrackableMeta):
|
|
6175
|
+
"""Create SAORGClient instance to authorize SDK in an organization scope.
|
|
6176
|
+
In case of no argument has been provided, SA_TOKEN environmental variable
|
|
6177
|
+
will be checked or $HOME/.superannotate/config.ini will be used.
|
|
6178
|
+
|
|
6179
|
+
Requires an Organization API key.
|
|
6180
|
+
|
|
6181
|
+
:param token: Organization API key
|
|
6182
|
+
:type token: str
|
|
6183
|
+
|
|
6184
|
+
:param config_path: path to config file
|
|
6185
|
+
:type config_path: str
|
|
6186
|
+
|
|
6187
|
+
:param config: configuration applied on creation, instead of or on top of a config
|
|
6188
|
+
file. Keys are a config file's keys - ``SA_TOKEN``, ``SA_URL``, ``SA_TEAM_ID``,
|
|
6189
|
+
``VERIFY_SSL``, ``LOGGING_LEVEL``, ``LOGGING_PATH``, ``ANNOTATION_CHUNK_SIZE``,
|
|
6190
|
+
``ITEM_CHUNK_SIZE``, ``MAX_THREAD_COUNT``, ``MAX_COROUTINE_COUNT`` - and an
|
|
6191
|
+
unrecognised one is an error rather than silently ignored. Explicit ``token``
|
|
6192
|
+
and ``team_id`` arguments win over the same keys here.
|
|
6193
|
+
:type config: dict
|
|
6194
|
+
"""
|
|
6195
|
+
|
|
6196
|
+
CONTROLLER_CLASS = OrgController
|
|
6197
|
+
|
|
6198
|
+
def __init__(
|
|
6199
|
+
self,
|
|
6200
|
+
token: str | None = None,
|
|
6201
|
+
config_path: str | None = None,
|
|
6202
|
+
*,
|
|
6203
|
+
config: dict | None = None,
|
|
6204
|
+
):
|
|
6205
|
+
super().__init__(token, config_path, config=config)
|
|
6206
|
+
|
|
6207
|
+
def get_team_client(self, team_id: int) -> SAClient:
|
|
6208
|
+
"""Returns a normal SAClient backed by the organization token, with team context returned.
|
|
6209
|
+
|
|
6210
|
+
:param team_id: ID of the team to operate in.
|
|
6211
|
+
:type team_id: int
|
|
6212
|
+
|
|
6213
|
+
:return: a team-scoped client exposing the full team-level SDK surface. The
|
|
6214
|
+
team is fixed at construction; team_id is available as a read-only
|
|
6215
|
+
property.
|
|
6216
|
+
:rtype: SAClient
|
|
6217
|
+
|
|
6218
|
+
Request Example:
|
|
6219
|
+
::
|
|
6220
|
+
|
|
6221
|
+
team_client = org_client.get_team_client(team_id=12345)
|
|
6222
|
+
team_client.list_projects(name__contains="My Project")
|
|
6223
|
+
"""
|
|
6224
|
+
# The whole configuration this client was built with, re-pointed at one team.
|
|
6225
|
+
# Passing the token alone would rebuild the config from defaults and lose
|
|
6226
|
+
# API_URL with it, which means silently moving to production.
|
|
6227
|
+
try:
|
|
6228
|
+
return SAClient(config={**self.controller.config, "SA_TEAM_ID": team_id})
|
|
6229
|
+
except SAAuthError:
|
|
6230
|
+
raise AppException(INVALID_TEAM_ID_ERROR)
|
|
6231
|
+
|
|
6232
|
+
def list_teams(self) -> list[dict]:
|
|
6233
|
+
"""Returns the teams in the given organization. Must use Organization API Key for this function.
|
|
6234
|
+
|
|
6235
|
+
:return: the organization's teams; an empty list if it has none.
|
|
6236
|
+
:rtype: list of dicts
|
|
6237
|
+
|
|
6238
|
+
Request Example:
|
|
6239
|
+
::
|
|
6240
|
+
|
|
6241
|
+
org_client.list_teams()
|
|
6242
|
+
"""
|
|
6243
|
+
response = self.controller.list_teams()
|
|
6244
|
+
return BaseSerializer.serialize_iterable(
|
|
6245
|
+
response.data or [], by_alias=True, exclude_unset=True
|
|
6246
|
+
)
|
|
@@ -18,16 +18,29 @@ from lib.core.enums import UserRole
|
|
|
18
18
|
|
|
19
19
|
CONFIG = Config()
|
|
20
20
|
BACKEND_URL = "https://api.superannotate.com"
|
|
21
|
-
|
|
21
|
+
#: Where the SDK keeps its config. The display form keeps the "~": a message shown to
|
|
22
|
+
#: a user should name the file, not spell out their home directory.
|
|
23
|
+
HOME_DISPLAY_PATH = "~/.superannotate"
|
|
24
|
+
HOME_PATH = expanduser(HOME_DISPLAY_PATH)
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
CONFIG_INI_FILE_LOCATION = CONFIG_INI_PATH
|
|
26
|
+
CONFIG_JSON_FILE_LOCATION = f"{HOME_PATH}/config.json"
|
|
27
|
+
CONFIG_INI_FILE_LOCATION = f"{HOME_PATH}/config.ini"
|
|
28
|
+
CONFIG_INI_DISPLAY_PATH = f"{HOME_DISPLAY_PATH}/config.ini"
|
|
27
29
|
|
|
28
30
|
LOG_FILE_LOCATION = f"{HOME_PATH}/logs"
|
|
29
31
|
DEFAULT_LOGGING_LEVEL = "INFO"
|
|
30
32
|
|
|
33
|
+
INVALID_TOKEN_ERROR = "Invalid token."
|
|
34
|
+
INVALID_TEAM_ID_ERROR = "Invalid team id provided."
|
|
35
|
+
INVALID_CREDENTIALS_ERROR = "Invalid credentials provided."
|
|
36
|
+
INVALID_TEAM_CONTEXT = (
|
|
37
|
+
'Team context not provided. An Organization API key requires a "team_id".'
|
|
38
|
+
)
|
|
39
|
+
CREDENTIALS_NOT_FOUND_ERROR = (
|
|
40
|
+
"Credentials not found: SA_TOKEN environment variable is not set and "
|
|
41
|
+
f"config file '{CONFIG_INI_DISPLAY_PATH}' was not found."
|
|
42
|
+
)
|
|
43
|
+
|
|
31
44
|
|
|
32
45
|
def setup_logging(level=DEFAULT_LOGGING_LEVEL, file_path=LOG_FILE_LOCATION):
|
|
33
46
|
logger = logging.getLogger("sa")
|
{superannotate-4.6.0 → superannotate-4.6.1.dev1}/src/superannotate/lib/core/entities/__init__.py
RENAMED
|
@@ -2,6 +2,8 @@ from lib.core.entities.base import BaseItemEntity
|
|
|
2
2
|
from lib.core.entities.base import ConfigEntity
|
|
3
3
|
from lib.core.entities.base import SubSetEntity
|
|
4
4
|
from lib.core.entities.classes import AnnotationClassEntity
|
|
5
|
+
from lib.core.entities.context import TokenContext
|
|
6
|
+
from lib.core.entities.context import TokenScope
|
|
5
7
|
from lib.core.entities.folder import FolderEntity
|
|
6
8
|
from lib.core.entities.integrations import IntegrationEntity
|
|
7
9
|
from lib.core.entities.items import CategoryEntity
|
|
@@ -16,6 +18,7 @@ from lib.core.entities.multimodal_form import FormModel
|
|
|
16
18
|
from lib.core.entities.multimodal_form import generate_classes_from_form
|
|
17
19
|
from lib.core.entities.project import AttachmentEntity
|
|
18
20
|
from lib.core.entities.project import CustomFieldEntity
|
|
21
|
+
from lib.core.entities.project import OrgTeamEntity
|
|
19
22
|
from lib.core.entities.project import ProjectEntity
|
|
20
23
|
from lib.core.entities.project import SettingEntity
|
|
21
24
|
from lib.core.entities.project import StepEntity
|
|
@@ -30,6 +33,8 @@ from lib.core.entities.work_managament import WMProjectUserEntity
|
|
|
30
33
|
__all__ = [
|
|
31
34
|
# base
|
|
32
35
|
"ConfigEntity",
|
|
36
|
+
"TokenContext",
|
|
37
|
+
"TokenScope",
|
|
33
38
|
"SettingEntity",
|
|
34
39
|
"SubSetEntity",
|
|
35
40
|
"CustomFieldEntity",
|
|
@@ -49,12 +54,12 @@ __all__ = [
|
|
|
49
54
|
"WorkflowEntity",
|
|
50
55
|
"CategoryEntity",
|
|
51
56
|
"WMProjectUserEntity",
|
|
52
|
-
"ConfigEntity",
|
|
53
57
|
"StepEntity",
|
|
54
58
|
"FolderEntity",
|
|
55
59
|
"S3FileEntity",
|
|
56
60
|
"AnnotationClassEntity",
|
|
57
61
|
"TeamEntity",
|
|
62
|
+
"OrgTeamEntity",
|
|
58
63
|
"UserEntity",
|
|
59
64
|
"IntegrationEntity",
|
|
60
65
|
"PROJECT_ITEM_ENTITY_MAP",
|