From ece2d6362874aa39514c6e69371b472f5760909d Mon Sep 17 00:00:00 2001 From: Jonas Maison Date: Thu, 5 Oct 2023 10:16:50 +0200 Subject: [PATCH 1/5] test: add e2e tests for primitives (#1519) --- .github/scripts/upload_test_stats_datadog.py | 14 ++++++ tests/e2e/test_mutations_asset.py | 46 +++++++++++++++++++ tests/e2e/test_mutations_label.py | 48 ++++++++++++++++++++ tests/e2e/test_projects.py | 14 ++++++ 4 files changed, 122 insertions(+) diff --git a/.github/scripts/upload_test_stats_datadog.py b/.github/scripts/upload_test_stats_datadog.py index 8cfc5728a..154c8e1ed 100644 --- a/.github/scripts/upload_test_stats_datadog.py +++ b/.github/scripts/upload_test_stats_datadog.py @@ -21,10 +21,24 @@ "tests/e2e/test_notebooks.py::test_all_recipes[tests/e2e/import_predictions.ipynb]": ( "import_predictions_ipynb" ), + "tests/e2e/test_notebooks.py::test_all_recipes[recipes/export_a_kili_project.ipynb]": ( + "export_a_kili_project_ipynb" + ), + "tests/e2e/test_notebooks.py::test_all_recipes[recipes/importing_video_assets.ipynb]": ( + "importing_video_assets_ipynb" + ), "tests/e2e/test_copy_project.py::test_copy_project_e2e_video": "copy_project_e2e_video", "tests/e2e/test_copy_project.py::test_copy_project_e2e_with_ocr_metadata": ( "copy_project_e2e_with_ocr_metadata" ), + "tests/e2e/test_projects.py::test_create_project": "create_project", + "tests/e2e/test_mutations_label.py::test_append_many_labels": "append_many_labels", + "tests/e2e/test_mutations_asset.py::test_append_many_assets": "append_many_assets", + "tests/e2e/test_mutations_asset.py::test_send_back_to_queue": "send_back_to_queue", + "tests/e2e/test_mutations_asset.py::test_delete_many_from_dataset": "delete_many_from_dataset", + "tests/e2e/test_mutations_asset.py::test_change_asset_external_ids": ( + "change_asset_external_ids" + ), } OWNER = "kili-technology" diff --git a/tests/e2e/test_mutations_asset.py b/tests/e2e/test_mutations_asset.py index 89c71c7c7..7d811a8d1 100644 --- a/tests/e2e/test_mutations_asset.py +++ b/tests/e2e/test_mutations_asset.py @@ -146,3 +146,49 @@ def test_update_properties_in_assets_external_id(kili, src_project): assert [asset["externalId"] for asset in assets_new] == ["1", "2", "3"] assert [asset["priority"] for asset in assets_new] == [1, 0, 0] + + +@pytest.fixture() +def src_project_no_assets(kili: Kili): + interface = { + "jobs": { + "DETECTION": { + "mlTask": "OBJECT_DETECTION", + "tools": ["rectangle"], + "instruction": "Is there a defect ? Where ? What kind ?", + "required": 0, + "isChild": False, + "content": { + "categories": { + "DEFECT_CLASS_1": {"name": "defect of class 1"}, + "DEFECT_CLASS_2": {"name": "defect of class 2"}, + "DEFECT_CLASS_3": {"name": "defect of class 3"}, + "DEFECT_CLASS_4": {"name": "defect of class 4"}, + }, + "input": "radio", + }, + } + } + } + + project = kili.create_project( + input_type="IMAGE", + json_interface=interface, + title="e2e test_add_many_assets", + ) + + yield project["id"] + + kili.delete_project(project["id"]) + + +def test_append_many_assets(kili: Kili, src_project_no_assets: str): + NB_ASSETS = 500 + img_url = "https://storage.googleapis.com/label-public-staging/car/car_1.jpg" + + kili.append_many_to_dataset( + project_id=src_project_no_assets, + content_array=[img_url] * NB_ASSETS, + ) + + assert kili.count_assets(src_project_no_assets) == NB_ASSETS diff --git a/tests/e2e/test_mutations_label.py b/tests/e2e/test_mutations_label.py index 2ba078875..f5ca2ba71 100644 --- a/tests/e2e/test_mutations_label.py +++ b/tests/e2e/test_mutations_label.py @@ -92,3 +92,51 @@ def test_e2e_append_labels_overwrite(kili: Kili, project: Dict): ] for label_id in old_pred_ids_asset_1: assert label_id not in new_label_ids + + +@pytest.fixture() +def project_and_asset_id(kili: Kili): + interface = { + "jobs": { + "CLASSIFICATION_JOB": { + "content": {"categories": {"A": {"children": [], "name": "A"}}, "input": "radio"}, + "instruction": "classif", + "mlTask": "CLASSIFICATION", + "required": 1, + "isChild": False, + } + } + } + + project = kili.create_project( + input_type="TEXT", + json_interface=interface, + title="test_append_many_labels", + ) + + kili.append_many_to_dataset( + project_id=project["id"], + content_array=["asset_content_1"], + external_id_array=["1"], + ) + + asset_id = kili.assets(project_id=project["id"], fields=("id",))[0] + + yield project["id"], asset_id["id"] + + kili.delete_project(project["id"]) + + +def test_append_many_labels(kili: Kili, project_and_asset_id): + project_id, asset_id = project_and_asset_id + + N_LABELS = 1000 + + kili.append_labels( + project_id=project_id, + asset_id_array=[asset_id] * N_LABELS, + json_response_array=[{"CLASSIFICATION_JOB": {"categories": [{"name": "A"}]}}] * N_LABELS, + label_type="DEFAULT", + ) + + assert kili.count_labels(project_id=project_id) == N_LABELS diff --git a/tests/e2e/test_projects.py b/tests/e2e/test_projects.py index 44f1868b0..34ef550fd 100644 --- a/tests/e2e/test_projects.py +++ b/tests/e2e/test_projects.py @@ -5,6 +5,20 @@ from kili.client import Kili +@pytest.fixture() +def project_name(kili: Kili): + project_name = "e2e test_create_project " + str(uuid.uuid4()) + + yield project_name + + project_id = kili.projects(search_query=f"%{project_name}%", first=1)[0]["id"] + kili.delete_project(project_id) + + +def test_create_project(project_name: str, kili: Kili): + _ = kili.create_project(input_type="TEXT", json_interface={}, title=project_name) + + @pytest.fixture() def projects_uuid(kili: Kili): projects_uuid = str(uuid.uuid4()) From 035f4248c314c938d47bb1932cb096f8304b7654 Mon Sep 17 00:00:00 2001 From: theodu <82399630+theodu@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:52:58 +0200 Subject: [PATCH 2/5] feat: remove status_array from mutations (#1521) Co-authored-by: theodu --- .../entrypoints/mutations/asset/__init__.py | 18 ++++++------------ .../entrypoints/mutations/asset/helpers.py | 2 -- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/kili/entrypoints/mutations/asset/__init__.py b/src/kili/entrypoints/mutations/asset/__init__.py index 615ccfb24..f032941f3 100644 --- a/src/kili/entrypoints/mutations/asset/__init__.py +++ b/src/kili/entrypoints/mutations/asset/__init__.py @@ -73,8 +73,7 @@ def append_many_to_dataset( If None, random identifiers are created. id_array: Disabled parameter. Do not use. is_honeypot_array: Whether to use the asset for honeypot - status_array: By default, all imported assets are set to `TODO`. Other options: - `ONGOING`, `LABELED`, `REVIEWED`. + status_array: DEPRECATED and does not have any effect. json_content_array: Useful for `VIDEO` or `TEXT` projects only. - For `VIDEO` projects, each element is a sequence of frames, i.e. a @@ -134,8 +133,8 @@ def append_many_to_dataset( if status_array is not None: warnings.warn( - "status_array is deprecated, asset status is automatically computed based on" - " its labels and cannot be overwritten.", + "status_array is deprecated and will not be sent in the call. Asset status is" + " automatically computed based on its labels and cannot be overwritten.", DeprecationWarning, stacklevel=1, ) @@ -154,7 +153,6 @@ def append_many_to_dataset( "json_content": json_content_array, "external_id": external_id_array, "id": id_array, - "status": status_array, "json_metadata": json_metadata_array, "is_honeypot": is_honeypot_array, } @@ -213,8 +211,7 @@ def update_properties_in_assets( is a text formatted using RichText. - For a Video project, the`json_content` is a json containg urls pointing to each frame of the video. - status_array: Each element should be in `TODO`, `ONGOING`, `LABELED`, - `TO_REVIEW`, `REVIEWED`. + status_array: DEPRECATED and does not have any effect. is_used_for_consensus_array: Whether to use the asset to compute consensus kpis or not. is_honeypot_array: Whether to use the asset for honeypot. project_id: The project ID. Only required if `external_ids` argument is provided. @@ -238,7 +235,6 @@ def update_properties_in_assets( is_honeypot_array=[True, True], is_used_for_consensus_array=[True, False], priorities=[None, 2], - status_array=['LABELED', 'REVIEWED'], to_be_labeled_by_array=[['test+pierre@kili-technology.com'], None], ) @@ -266,12 +262,11 @@ def update_properties_in_assets( if status_array is not None: warnings.warn( - "status_array is deprecated, asset status is automatically computed based on" - " its labels and cannot be overwritten.", + "status_array is deprecated and will not be sent in the call. Asset status is" + " automatically computed based on its labels and cannot be overwritten.", DeprecationWarning, stacklevel=1, ) - if asset_ids is not None and external_ids is not None: warnings.warn( "The use of `external_ids` argument has changed. It is now used to identify" @@ -294,7 +289,6 @@ def update_properties_in_assets( to_be_labeled_by_array=to_be_labeled_by_array, contents=contents, json_contents=json_contents, - status_array=status_array, is_used_for_consensus_array=is_used_for_consensus_array, is_honeypot_array=is_honeypot_array, resolution_array=resolution_array, diff --git a/src/kili/entrypoints/mutations/asset/helpers.py b/src/kili/entrypoints/mutations/asset/helpers.py index 24c85ae19..db584aa9f 100644 --- a/src/kili/entrypoints/mutations/asset/helpers.py +++ b/src/kili/entrypoints/mutations/asset/helpers.py @@ -17,7 +17,6 @@ def process_update_properties_in_assets_parameters( to_be_labeled_by_array: Optional[List[List[str]]] = None, contents: Optional[List[str]] = None, json_contents: Optional[List[str]] = None, - status_array: Optional[List[str]] = None, is_used_for_consensus_array: Optional[List[bool]] = None, is_honeypot_array: Optional[List[bool]] = None, resolution_array: Optional[List[dict]] = None, @@ -47,7 +46,6 @@ def process_update_properties_in_assets_parameters( "shouldResetToBeLabeledBy": to_be_labeled_by_array, "content": contents, "jsonContent": json_contents, - "status": status_array, "isUsedForConsensus": is_used_for_consensus_array, "isHoneypot": is_honeypot_array, "pageResolutions": page_resolutions_array, From 9394e70a952296f7d286b8ad8c4083e4a837f873 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 07:32:24 +0200 Subject: [PATCH 3/5] chore(deps): update dependency dev/pylint to v3.0.1 (#1525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 68cd11a79..13995b336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ dev = [ # linting "black", "pre-commit >= 3.3.0, < 4.0.0", - "pylint ==3.0.0", + "pylint ==3.0.1", "pyright == 1.1.329", # notebooks tests "nbformat", From 8d0da6f4ab4b0683991a6c1507ea9dc555dc7c21 Mon Sep 17 00:00:00 2001 From: theodu <82399630+theodu@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:21:30 +0200 Subject: [PATCH 4/5] fix: min max zoom arg doc and doc building (#1523) Co-authored-by: theodu --- .github/workflows/ci.yml | 3 ++- docs/sdk/issue.md | 2 -- docs/sdk/project.md | 2 -- src/kili/entrypoints/mutations/asset/__init__.py | 4 +++- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e52c67bc..988e7ff89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,10 +139,11 @@ jobs: - name: Build run: | - mkdocs build 2>&1 | tee doc-build.log + mkdocs build - name: Look for Warnings run: | + mkdocs build 2>&1 | tee doc-build.log if grep -q "WARNING" doc-build.log; then echo $(grep "WARNING" doc-build.log) echo "::error::Documentation build completed with warnings" diff --git a/docs/sdk/issue.md b/docs/sdk/issue.md index 59d28c60d..bcb3525db 100644 --- a/docs/sdk/issue.md +++ b/docs/sdk/issue.md @@ -1,5 +1,3 @@ # Issue module ::: kili.presentation.client.issue.IssueClientMethods -::: kili.entrypoints.queries.issue.__init__.QueriesIssue -::: kili.entrypoints.mutations.issue.__init__.MutationsIssue diff --git a/docs/sdk/project.md b/docs/sdk/project.md index a3a665928..ff7315bdc 100644 --- a/docs/sdk/project.md +++ b/docs/sdk/project.md @@ -2,6 +2,4 @@ ::: kili.presentation.client.project.ProjectClientMethods -::: kili.entrypoints.queries.project.__init__.QueriesProject - ::: kili.entrypoints.mutations.project.__init__.MutationsProject diff --git a/src/kili/entrypoints/mutations/asset/__init__.py b/src/kili/entrypoints/mutations/asset/__init__.py index f032941f3..b77ae5744 100644 --- a/src/kili/entrypoints/mutations/asset/__init__.py +++ b/src/kili/entrypoints/mutations/asset/__init__.py @@ -87,7 +87,9 @@ def append_many_to_dataset( Example for one asset: `json_metadata_array = [{'imageUrl': '','text': '','url': ''}]`. - For VIDEO projects (and not VIDEO_LEGACY), you can specify a value with key 'processingParameters' to specify the sampling rate (default: 30). Example for one asset: `json_metadata_array = [{'processingParameters': {'framesPlayedPerSecond': 10}}]`. - - For Image projects, if you work with geotiff, you can specify a value with key 'processingParameters' to specify the minimum and maximum zoom level. + - In Image projects with geoTIFF assets, you can specify the `minZoom` and `maxZoom` values for the `processingParameters` key. + The `minZoom` parameter defines the zoom level that users are not allowed to zoom out from. + The `maxZoom` value affects asset generation: the higher the value, the greater the level of details and the size of the asset. Example for one asset: `json_metadata_array = [{'processingParameters': {'minZoom': 17, 'maxZoom': 19}}]`. disable_tqdm: If `True`, the progress bar will be disabled wait_until_availability: If `True`, the function will return once the assets are fully imported in Kili. From 2886b02a69830324b889210515155b2ba042292c Mon Sep 17 00:00:00 2001 From: Jonas Maison Date: Fri, 6 Oct 2023 11:24:47 +0200 Subject: [PATCH 5/5] fix: use projects resolver to check for data connections (#1522) --- src/kili/services/copy_project/__init__.py | 4 +- src/kili/services/export/format/base.py | 15 +---- src/kili/use_cases/asset/media_downloader.py | 18 ++---- tests/e2e/test_notebooks.py | 4 +- tests/fakes/fake_kili.py | 4 ++ tests/integration/use_cases/test_asset.py | 2 +- tests/unit/services/export/test_coco.py | 10 +-- tests/unit/services/export/test_export.py | 67 ++++++++++++++++++-- tests/unit/services/export/test_geojson.py | 6 +- tests/unit/services/export/test_kili.py | 3 + tests/unit/services/export/test_voc.py | 11 ++-- tests/unit/services/export/test_yolo.py | 3 + 12 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/kili/services/copy_project/__init__.py b/src/kili/services/copy_project/__init__.py index a89d69ae2..4a33f42e7 100644 --- a/src/kili/services/copy_project/__init__.py +++ b/src/kili/services/copy_project/__init__.py @@ -26,7 +26,7 @@ class ProjectCopier: # pylint: disable=too-few-public-methods "inputType", "description", "id", - "dataConnections.dataIntegrationId", + "dataConnections.id", ) FIELDS_JSON_INTERFACE = ("jsonInterface",) FIELDS_QUALITY_SETTINGS = ( @@ -87,7 +87,7 @@ def copy_project( # pylint: disable=too-many-arguments,too-many-locals src_project = get_project(self.kili, from_project_id, fields) - if len(src_project["dataConnections"]) > 0 and copy_assets: + if src_project["dataConnections"] and copy_assets: raise NotImplementedError("Copying projects with cloud storage is not supported.") new_project_title = title or self._generate_project_title(src_title=src_project["title"]) diff --git a/src/kili/services/export/format/base.py b/src/kili/services/export/format/base.py index 7f262e26f..e37aaf4ed 100644 --- a/src/kili/services/export/format/base.py +++ b/src/kili/services/export/format/base.py @@ -10,11 +10,6 @@ from pathlib import Path from typing import Dict, List, NamedTuple, Optional, Tuple, cast -from kili.adapters.kili_api_gateway.helpers.queries import QueryOptions -from kili.core.graphql.operations.data_connection.queries import ( - DataConnectionsQuery, - DataConnectionsWhere, -) from kili.domain.asset import AssetId from kili.domain.project import ProjectId from kili.orm import Asset, Label @@ -206,14 +201,8 @@ def _check_and_ensure_asset_access(self) -> None: ) def _has_data_connection(self) -> bool: - data_connections_gen = DataConnectionsQuery( - self.kili.graphql_client, self.kili.http_client - )( - where=DataConnectionsWhere(project_id=self.project_id), - fields=["id"], - options=QueryOptions(disable_tqdm=True, first=1, skip=0), - ) - return len(list(data_connections_gen)) > 0 + project = get_project(self.kili, self.project_id, ["dataConnections.id"]) + return bool(project["dataConnections"]) def _check_geotiff_export_compatibility(self, assets: List[Asset]) -> None: # pylint: disable=line-too-long diff --git a/src/kili/use_cases/asset/media_downloader.py b/src/kili/use_cases/asset/media_downloader.py index aeaf2d06b..a5d277d6d 100644 --- a/src/kili/use_cases/asset/media_downloader.py +++ b/src/kili/use_cases/asset/media_downloader.py @@ -13,11 +13,6 @@ from kili.adapters.http_client import HttpClient from kili.adapters.kili_api_gateway import KiliAPIGateway -from kili.adapters.kili_api_gateway.helpers.queries import QueryOptions -from kili.core.graphql.operations.data_connection.queries import ( - DataConnectionsQuery, - DataConnectionsWhere, -) from kili.domain.project import ProjectId from kili.domain.types import ListOrTuple @@ -41,19 +36,14 @@ def get_download_assets_function( if not download_media: return None, fields - project = kili_api_gateway.get_project(project_id=project_id, fields=("inputType",)) + project = kili_api_gateway.get_project( + project_id=project_id, fields=("inputType", "dataConnections.id") + ) input_type = project["inputType"] # We need to query the data connections to know if the assets are hosted in a cloud storage # If so, we remove the fields "content" and "jsonContent" from the query - data_connections_gen = DataConnectionsQuery( - kili_api_gateway.graphql_client, kili_api_gateway.http_client - )( - where=DataConnectionsWhere(project_id=project_id), - fields=("id",), - options=QueryOptions(disable_tqdm=True, first=1, skip=0), - ) - if len(list(data_connections_gen)) > 0: + if project["dataConnections"]: raise DownloadNotAllowedError( "The download of assets from a project connected to a cloud storage is not allowed." " Asset download is disabled." diff --git a/tests/e2e/test_notebooks.py b/tests/e2e/test_notebooks.py index eb007e225..64664e780 100644 --- a/tests/e2e/test_notebooks.py +++ b/tests/e2e/test_notebooks.py @@ -25,7 +25,7 @@ def process_notebook(notebook_filename: str) -> None: pytest.param( "tests/e2e/plugin_workflow.ipynb", marks=pytest.mark.skipif( - "lts.cloud" in os.environ["KILI_API_ENDPOINT"], + "lts.cloud" in os.getenv("KILI_API_ENDPOINT", ""), reason="Feature not available on premise", ), ), @@ -50,7 +50,7 @@ def process_notebook(notebook_filename: str) -> None: pytest.param( "recipes/plugins_example.ipynb", marks=pytest.mark.skipif( - "lts.cloud" in os.environ["KILI_API_ENDPOINT"], + "lts.cloud" in os.getenv("KILI_API_ENDPOINT", ""), reason="Feature not available on premise", ), ), diff --git a/tests/fakes/fake_kili.py b/tests/fakes/fake_kili.py index 27b4d3785..76cf668f5 100644 --- a/tests/fakes/fake_kili.py +++ b/tests/fakes/fake_kili.py @@ -93,6 +93,7 @@ def mocked_ProjectQuery(where, _fields, _options): "description": "This is a test project", "jsonInterface": json_interface, "inputType": "IMAGE", + "dataConnections": None, } ] elif project_id == "object_detection_video_project": @@ -122,6 +123,7 @@ def mocked_ProjectQuery(where, _fields, _options): "description": "This is a test project", "jsonInterface": json_interface, "inputType": "VIDEO", + "dataConnections": None, } ] elif project_id == "text_classification": @@ -155,6 +157,7 @@ def mocked_ProjectQuery(where, _fields, _options): "description": "This is a TC test project", "jsonInterface": json_interface, "inputType": "TEXT", + "dataConnections": None, } ] elif project_id == "semantic_segmentation": @@ -198,6 +201,7 @@ def mocked_ProjectQuery(where, _fields, _options): "description": "This is a semantic segmentation test project", "jsonInterface": json_interface, "inputType": "IMAGE", + "dataConnections": None, } ] else: diff --git a/tests/integration/use_cases/test_asset.py b/tests/integration/use_cases/test_asset.py index 0007fc80c..73eb413f0 100644 --- a/tests/integration/use_cases/test_asset.py +++ b/tests/integration/use_cases/test_asset.py @@ -110,7 +110,7 @@ def test_given_query_parameters_I_can_query_assets_and_download_their_media( kili_api_gateway: KiliAPIGateway, mocker ): # mocking - kili_api_gateway.get_project.return_value = {"inputType": "IMAGE"} + kili_api_gateway.get_project.return_value = {"inputType": "IMAGE", "dataConnections": None} media_downlaoder_mock = mocker.patch.object(MediaDownloader, "__init__", return_value=None) # given parameters to query assets diff --git a/tests/unit/services/export/test_coco.py b/tests/unit/services/export/test_coco.py index d9d44a25f..b173fec9f 100644 --- a/tests/unit/services/export/test_coco.py +++ b/tests/unit/services/export/test_coco.py @@ -16,7 +16,6 @@ _get_coco_categories_with_mapping, _get_coco_geometry_from_kili_bpoly, ) -from kili.services.export.format.kili import KiliExporter from kili.services.types import Job, JobName from kili.utils.tempfile import TemporaryDirectory @@ -579,12 +578,9 @@ def test_when_exporting_to_coco_given_a_project_with_data_connection_then_it_sho mocker.patch( "kili.services.export.format.base.get_project", return_value=get_project_return_val ) - mocker.patch.object(KiliExporter, "_check_arguments_compatibility", return_value=None) - mocker.patch.object(KiliExporter, "_check_project_compatibility", return_value=None) - mocker.patch( - "kili.services.export.format.base.DataConnectionsQuery.__call__", - return_value=(i for i in [{"id": "fake_data_connection_id"}]), - ) + mocker.patch.object(CocoExporter, "_check_arguments_compatibility", return_value=None) + mocker.patch.object(CocoExporter, "_check_project_compatibility", return_value=None) + mocker.patch.object(CocoExporter, "_has_data_connection", return_value=True) kili = QueriesLabel() kili.api_key = "" # type: ignore diff --git a/tests/unit/services/export/test_export.py b/tests/unit/services/export/test_export.py index be5f07e08..6941b04f6 100644 --- a/tests/unit/services/export/test_export.py +++ b/tests/unit/services/export/test_export.py @@ -14,13 +14,14 @@ from kili.domain.asset import AssetFilters from kili.entrypoints.queries.label import QueriesLabel from kili.orm import Asset -from kili.services.export import export_labels +from kili.services.export import AbstractExporter, export_labels from kili.services.export.exceptions import ( NoCompatibleJobError, NotCompatibleInputType, NotCompatibleOptions, ) from kili.services.export.format.kili import KiliExporter +from kili.services.export.format.voc import VocExporter from tests.fakes.fake_kili import ( FakeKili, mocked_AssetQuery, @@ -794,6 +795,7 @@ def test_export_with_asset_filter_kwargs(mocker): "kili.services.export.format.base.get_project", return_value=get_project_return_val ) mocker.patch.object(KiliExporter, "process_and_save", return_value=None) + mocker.patch.object(KiliExporter, "_has_data_connection", return_value=False) kili = QueriesLabel() kili.api_endpoint = "https://" # type: ignore kili.api_key = "" # type: ignore @@ -875,6 +877,7 @@ def test_export_with_asset_filter_kwargs_unknown_arg(mocker): ) mocker.patch.object(KiliExporter, "_check_arguments_compatibility", return_value=None) mocker.patch.object(KiliExporter, "_check_project_compatibility", return_value=None) + mocker.patch.object(KiliExporter, "_has_data_connection", return_value=False) kili = QueriesLabel() kili.api_endpoint = "https://" # type: ignore kili.api_key = "" # type: ignore @@ -917,11 +920,7 @@ def mock_kili(mocker, with_data_connection): mocker.patch( "kili.services.export.format.base.get_project", return_value=get_project_return_val ) - if with_data_connection: - mocker.patch( - "kili.services.export.format.base.DataConnectionsQuery.__call__", - return_value=(i for i in [{"id": "fake_data_connection_id"}]), - ) + mocker.patch.object(AbstractExporter, "_has_data_connection", return_value=with_data_connection) kili = QueriesLabel() kili.kili_api_gateway = mocker.MagicMock() @@ -1050,3 +1049,59 @@ def test_when_exporting_geotiff_asset_with_incompatible_options_then_it_crashes( ), ): kili.export_labels("fake_proj_id", "export.zip", fmt="kili", normalized_coordinates=False) + + +def test_given_kili_when_exporting_it_does_not_call_dataconnection_resolver( + mocker: pytest_mock.MockerFixture, +): + """Test that the dataconnection resolver is not called when exporting. + + Export for projects with data connections is forbidden. + But dataConnections() resolver requires high permissions. + This test ensures that the resolver is not called when exporting. + """ + # Given + project_return_val = { + "jsonInterface": { + "jobs": { + "OBJECT_DETECTION_JOB": { + "content": { + "categories": { + "GDGF": { + "children": [], + "color": "#472CED", + "name": "gdgf", + } + }, + "input": "radio", + }, + "instruction": "df", + "mlTask": "OBJECT_DETECTION", + "required": 1, + "tools": ["rectangle"], + "isChild": False, + } + } + }, + "inputType": "IMAGE", + "title": "", + "dataConnections": None, + } + mocker.patch.object(ProjectQuery, "__call__", return_value=[project_return_val]) + mocker.patch("kili.services.export.format.base.fetch_assets", return_value=[]) + process_and_save_mock = mocker.patch.object(VocExporter, "process_and_save", return_value=None) + kili = QueriesLabel() + kili.api_endpoint = "https://" # type: ignore + kili.api_key = "" # type: ignore + kili.graphql_client = mocker.MagicMock() + kili.http_client = mocker.MagicMock() + kili.kili_api_gateway = mocker.MagicMock() + + # When + kili.export_labels( + project_id="fake_proj_id", filename="exp.zip", fmt="pascal_voc", layout="merged" + ) + + # Then + process_and_save_mock.assert_called_once() + kili.graphql_client.execute.assert_not_called() diff --git a/tests/unit/services/export/test_geojson.py b/tests/unit/services/export/test_geojson.py index 51ab7036b..5c13da0f3 100644 --- a/tests/unit/services/export/test_geojson.py +++ b/tests/unit/services/export/test_geojson.py @@ -7,6 +7,7 @@ from kili.entrypoints.queries.label import QueriesLabel from kili.orm import Asset +from kili.services.export.format.geojson import GeoJsonExporter def test_kili_export_labels_geojson(mocker: pytest_mock.MockerFixture): @@ -22,10 +23,7 @@ def test_kili_export_labels_geojson(mocker: pytest_mock.MockerFixture): mocker.patch( "kili.services.export.format.base.get_project", return_value=get_project_return_val ) - mocker.patch( - "kili.services.export.format.base.DataConnectionsQuery.__call__", - return_value=(i for i in [{"id": "fake_data_connection_id"}]), - ) + mocker.patch.object(GeoJsonExporter, "_has_data_connection", return_value=False) mocker.patch( "kili.services.export.format.base.fetch_assets", return_value=[ diff --git a/tests/unit/services/export/test_kili.py b/tests/unit/services/export/test_kili.py index c3c7f0f76..668262383 100644 --- a/tests/unit/services/export/test_kili.py +++ b/tests/unit/services/export/test_kili.py @@ -189,6 +189,7 @@ def test_kili_exporter_convert_to_pixel_coords_pdf(mocker: pytest_mock.MockerFix def test_kili_export_labels_non_normalized_pdf(mocker: pytest_mock.MockerFixture): get_project_return_val = { "inputType": "PDF", + "dataConnections": None, "id": "fake_proj_id", "title": "fake_proj_title", "description": "fake_proj_description", @@ -273,6 +274,7 @@ def test_kili_export_labels_non_normalized_image(mocker: pytest_mock.MockerFixtu get_project_return_val = { "id": "fake_proj_id", "title": "hgfhfg", + "dataConnections": None, "inputType": "IMAGE", "jsonInterface": { "jobs": { @@ -444,6 +446,7 @@ def test_kili_export_labels_non_normalized_video(mocker: pytest_mock.MockerFixtu "title": "Object tracking on video", "description": "Use bounding-box to track objects across video frames.", "id": "fake_proj_id", + "dataConnections": None, } mocker.patch("kili.services.export.get_project", return_value=get_project_return_val) diff --git a/tests/unit/services/export/test_voc.py b/tests/unit/services/export/test_voc.py index 99a7cbdad..9d5335874 100644 --- a/tests/unit/services/export/test_voc.py +++ b/tests/unit/services/export/test_voc.py @@ -4,7 +4,10 @@ from kili.entrypoints.queries.label import QueriesLabel from kili.services.export.exceptions import NotCompatibleOptions -from kili.services.export.format.voc import _convert_from_kili_to_voc_format +from kili.services.export.format.voc import ( + VocExporter, + _convert_from_kili_to_voc_format, +) from tests.fakes.fake_data import asset_image_1, asset_image_1_without_annotation @@ -44,15 +47,13 @@ def test_when_exporting_to_voc_given_a_project_with_data_connection_then_it_shou "inputType": "IMAGE", "title": "", "id": "fake_proj_id", + "dataConnections": None, } mocker.patch("kili.services.export.get_project", return_value=get_project_return_val) mocker.patch( "kili.services.export.format.base.get_project", return_value=get_project_return_val ) - mocker.patch( - "kili.services.export.format.base.DataConnectionsQuery.__call__", - return_value=(i for i in [{"id": "fake_data_connection_id"}]), - ) + mocker.patch.object(VocExporter, "_has_data_connection", return_value=True) kili = QueriesLabel() kili.api_endpoint = "https://" # type: ignore diff --git a/tests/unit/services/export/test_yolo.py b/tests/unit/services/export/test_yolo.py index 1ea561784..15fca510c 100644 --- a/tests/unit/services/export/test_yolo.py +++ b/tests/unit/services/export/test_yolo.py @@ -10,6 +10,7 @@ from kili.entrypoints.queries.label import QueriesLabel from kili.orm import Asset from kili.services.export.format.yolo import ( + YoloExporter, _convert_from_kili_to_yolo_format, _process_asset, _write_class_file, @@ -283,6 +284,7 @@ def test_yolo_v8_merged(mocker: pytest_mock.MockerFixture): "kili.services.export.format.base.fetch_assets", return_value=[Asset(asset) for asset in assets], ) + mocker.patch.object(YoloExporter, "_has_data_connection", return_value=False) kili = QueriesLabel() kili.api_endpoint = "https://" # type: ignore @@ -334,6 +336,7 @@ def test_yolo_v8_split_jobs(mocker: pytest_mock.MockerFixture): "kili.services.export.format.base.fetch_assets", return_value=[Asset(asset) for asset in assets], ) + mocker.patch.object(YoloExporter, "_has_data_connection", return_value=False) kili = QueriesLabel() kili.api_endpoint = "https://" # type: ignore