diff --git a/darwin/future/core/properties/__init__.py b/darwin/future/core/properties/__init__.py new file mode 100644 index 000000000..ef53ea047 --- /dev/null +++ b/darwin/future/core/properties/__init__.py @@ -0,0 +1,5 @@ +from darwin.future.core.properties.get import ( + get_property_by_id, + get_team_full_properties, + get_team_properties, +) diff --git a/darwin/future/core/properties/get.py b/darwin/future/core/properties/get.py new file mode 100644 index 000000000..23a80a380 --- /dev/null +++ b/darwin/future/core/properties/get.py @@ -0,0 +1,72 @@ +from typing import List, Optional, Union +from uuid import UUID + +from pydantic import parse_obj_as + +from darwin.future.core.client import ClientCore +from darwin.future.core.types.common import QueryString +from darwin.future.data_objects.properties import FullProperty + + +def get_team_properties( + client: ClientCore, + team_slug: Optional[str] = None, + params: Optional[QueryString] = None, +) -> List[FullProperty]: + """ + Returns a List[FullProperty] object for the specified team slug. + + Parameters: + client (ClientCore): The client to use for the request. + team_slug (Optional[str]): The slug of the team to get. If not specified, the + default team from the client's config will be used. + + Returns: + List[FullProperty]: List of FullProperty objects for the specified team slug. + + Raises: + HTTPError: If the response status code is not in the 200-299 range. + """ + if not team_slug: + team_slug = client.config.default_team + response = client.get(f"/v2/teams/{team_slug}/properties", query_string=params) + assert isinstance(response, dict) + return parse_obj_as(List[FullProperty], response.get("properties")) + + +def get_team_full_properties( + client: ClientCore, + team_slug: Optional[str] = None, + params: Optional[QueryString] = None, +) -> List[FullProperty]: + params = ( + params + QueryString({"include_values": True}) + if params and not params.get("include_values") + else QueryString({"include_values": True}) + ) + return get_team_properties(client, team_slug, params) + + +def get_property_by_id( + client: ClientCore, property_id: Union[str, UUID], team_slug: Optional[str] = None +) -> FullProperty: + """ + Returns a FullProperty object for the specified team slug. + + Parameters: + client (ClientCore): The client to use for the request. + property_id (str | UUID): The ID of the property to get. + team_slug (Optional[str]): The slug of the team to get. If not specified, the + default team from the client's config will be used. + + Returns: + FullProperty: FullProperty object from id + + Raises: + HTTPError: If the response status code is not in the 200-299 range. + """ + if not team_slug: + team_slug = client.config.default_team + response = client.get(f"/v2/teams/{team_slug}/properties/{property_id}") + assert isinstance(response, dict) + return parse_obj_as(FullProperty, response) diff --git a/darwin/future/core/types/common.py b/darwin/future/core/types/common.py index 9725c2d64..a282e51f5 100644 --- a/darwin/future/core/types/common.py +++ b/darwin/future/core/types/common.py @@ -101,10 +101,13 @@ def __str__(self) -> str: for k, v in self.value.items(): if isinstance(v, list): for x in v: - output += f"{k}={x}&" + output += f"{k}={x.lower()}&" else: - output += f"{k}={v}&" + output += f"{k}={v.lower()}&" return output[:-1] # remove trailing & def __add__(self, other: QueryString) -> QueryString: return QueryString({**self.value, **other.value}) + + def get(self, key: str, default: str = "") -> List[str] | str: + return self.value.get(key, default) diff --git a/darwin/future/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 83afe5e54..8c7434416 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -8,11 +8,39 @@ from darwin.future.core.client import ClientCore, DarwinConfig from darwin.future.data_objects.dataset import DatasetCore from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot +from darwin.future.data_objects.properties import FullProperty, PropertyOption from darwin.future.data_objects.team import TeamCore, TeamMemberCore from darwin.future.data_objects.team_member_role import TeamMemberRole from darwin.future.data_objects.workflow import WorkflowCore +@pytest.fixture +def base_property_option() -> PropertyOption: + return PropertyOption( + id="0", + position=0, + type="text", + value="test-value", + color="rgba(0,0,0,0)", + ) + + +@pytest.fixture +def base_property_object(base_property_option: PropertyOption) -> FullProperty: + return FullProperty( + id="0", + name="test-property", + type="text", + description="test-description", + required=False, + slug="test-property", + team_id=0, + annotation_class_id=0, + property_values=[base_property_option], + options=[base_property_option], + ) + + @pytest.fixture def base_config() -> DarwinConfig: return DarwinConfig( diff --git a/darwin/future/tests/core/properties/__init__.py b/darwin/future/tests/core/properties/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/darwin/future/tests/core/properties/test_get.py b/darwin/future/tests/core/properties/test_get.py new file mode 100644 index 000000000..3b2b5c2ba --- /dev/null +++ b/darwin/future/tests/core/properties/test_get.py @@ -0,0 +1,82 @@ +import responses +from responses.matchers import query_param_matcher + +from darwin.future.core.client import ClientCore +from darwin.future.core.properties import ( + get_property_by_id, + get_team_full_properties, + get_team_properties, +) +from darwin.future.core.types.common import QueryString +from darwin.future.data_objects.properties import FullProperty +from darwin.future.tests.core.fixtures import * + + +@responses.activate +def test_get_team_properties( + base_client: ClientCore, base_property_object: FullProperty +) -> None: + # Mocking the response using responses library + base_property_object.options = None + base_property_object.property_values = None + response_data = {"properties": [base_property_object.dict()]} + responses.add( + responses.GET, + f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties", + json=response_data, + status=200, + ) + + # Call the function being tested + properties = get_team_properties(base_client) + + # Assertions + assert isinstance(properties, list) + assert all(isinstance(property, FullProperty) for property in properties) + assert properties[0] == base_property_object + + +@responses.activate +def test_get_team_full_properties( + base_client: ClientCore, base_property_object: FullProperty +) -> None: + # Mocking the response using responses library + response_data = {"properties": [base_property_object.dict()]} + responses.add( + responses.GET, + f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties", + match=[ + query_param_matcher({"include_values": "true"}), + ], + json=response_data, + status=200, + ) + params = QueryString({"include_values": True}) + # Call the function being tested + properties = get_team_full_properties(base_client, params=params) + + # Assertions + assert isinstance(properties, list) + assert all(isinstance(property, FullProperty) for property in properties) + assert properties[0] == base_property_object + + +@responses.activate +def test_get_property_by_id( + base_client: ClientCore, base_property_object: FullProperty +) -> None: + # Mocking the response using responses library + property_id = "0" + responses.add( + responses.GET, + f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties/{property_id}", + json=base_property_object.dict(), + status=200, + ) + + # Call the function being tested + property = get_property_by_id(base_client, property_id) + + # Assertions + assert isinstance(property, FullProperty) + assert property == base_property_object diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index cda91ad10..36fd30767 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -40,7 +40,7 @@ def test_polygon_annotation(raw_json: dict): PolygonAnnotation.parse_obj(polygon_annotation) -def test_polygon_bbx_vaidator(raw_json: dict): +def test_polygon_bbx_validator(raw_json: dict): polygon_annotation = raw_json["annotations"][2] without_bbx = polygon_annotation.copy() del without_bbx["bounding_box"] @@ -50,3 +50,5 @@ def test_polygon_bbx_vaidator(raw_json: dict): assert without_bb_annotation.bounding_box is not None assert with_bb_annotation.bounding_box is not None assert without_bb_annotation == with_bb_annotation + bounds_annotation = raw_json["annotations"][0] + BoundingBoxAnnotation.parse_obj(bounds_annotation)