From 25d15453d54db7a21c154478530ec3628a1636ce Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:18:11 +0000 Subject: [PATCH 01/39] base annotation darwin --- darwin/future/data_objects/darwin.py | 67 ++++++++++++++++++++++++++ darwin/future/data_objects/property.py | 0 2 files changed, 67 insertions(+) create mode 100644 darwin/future/data_objects/darwin.py create mode 100644 darwin/future/data_objects/property.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py new file mode 100644 index 000000000..35032b7c0 --- /dev/null +++ b/darwin/future/data_objects/darwin.py @@ -0,0 +1,67 @@ +from typing import List, Literal, Optional, Union + +from pydantic import AnyUrl, validator + +from darwin.future.pydantic_base import DefaultDarwin + + +class Point(DefaultDarwin): + x: float + y: float + +class Polygon(DefaultDarwin): + paths: list[list[Point]] + +class AnnotationBase(DefaultDarwin): + id: str + name: str + properties: Optional[dict] = None + slot_names: Optional[List[str]] = None + +class BoundingBox(DefaultDarwin): + h: float + w: float + x: float + y: float + +class BoundingBoxAnnotation(AnnotationBase): + bouding_box: BoundingBox + +class Ellipse(DefaultDarwin): + center: Point + radius: Point + angle: float + +class EllipseAnnotation(AnnotationBase): + ellipse: Ellipse + +class PolygonAnnotation(AnnotationBase): + polygon: Polygon + bounding_box: Optional[BoundingBox] = None + + @validator('bounding_box', always=True) + def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: + if v is None: + raise NotImplementedError("TODO: Implement bounding box from polygon") + return v + +class FrameAnnotation(AnnotationBase): + frames: list + interpolated: bool + interpolate_algorithm: str + ranges: list[int] + + +AllowedAnnotation = Union[BoundingBoxAnnotation, FrameAnnotation, EllipseAnnotation, PolygonAnnotation] + +class DarwinV2(DefaultDarwin): + version: Literal["2.0"] = "2.0" + schema_ref: AnyUrl + item: dict + annotations: List[AllowedAnnotation] + + +class Item(DefaultDarwin): + name: str + path: str + \ No newline at end of file diff --git a/darwin/future/data_objects/property.py b/darwin/future/data_objects/property.py new file mode 100644 index 000000000..e69de29bb From 5aae6a69497ac35fdaf1f446095ddbcbf422fdc1 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:42:26 +0000 Subject: [PATCH 02/39] fixes for annotations --- darwin/future/data_objects/darwin.py | 44 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 35032b7c0..196d932a0 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -1,33 +1,36 @@ -from typing import List, Literal, Optional, Union +from __future__ import annotations -from pydantic import AnyUrl, validator +from typing import List, Literal, Optional, Union -from darwin.future.pydantic_base import DefaultDarwin +from pydantic import AnyUrl, BaseModel, validator -class Point(DefaultDarwin): +class Point(BaseModel): x: float y: float -class Polygon(DefaultDarwin): - paths: list[list[Point]] +class PolygonPath(BaseModel): + points: List[Point] + +class Polygon(BaseModel): + paths: List[PolygonPath] -class AnnotationBase(DefaultDarwin): +class AnnotationBase(BaseModel): id: str name: str properties: Optional[dict] = None slot_names: Optional[List[str]] = None -class BoundingBox(DefaultDarwin): +class BoundingBox(BaseModel): h: float w: float x: float y: float class BoundingBoxAnnotation(AnnotationBase): - bouding_box: BoundingBox + bounding_box: BoundingBox -class Ellipse(DefaultDarwin): +class Ellipse(BaseModel): center: Point radius: Point angle: float @@ -42,26 +45,33 @@ class PolygonAnnotation(AnnotationBase): @validator('bounding_box', always=True) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: - raise NotImplementedError("TODO: Implement bounding box from polygon") + h, w, x, y = 0.0, 0.0, 0.0, 0.0 + for point in values['polygon']['paths']: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) + v = BoundingBox(h=h, w=w, x=x, y=y) return v class FrameAnnotation(AnnotationBase): - frames: list + frames: List interpolated: bool interpolate_algorithm: str - ranges: list[int] + ranges: List[int] -AllowedAnnotation = Union[BoundingBoxAnnotation, FrameAnnotation, EllipseAnnotation, PolygonAnnotation] +AllowedAnnotation = Union[BoundingBoxAnnotation, EllipseAnnotation, PolygonAnnotation] -class DarwinV2(DefaultDarwin): +class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: AnyUrl item: dict annotations: List[AllowedAnnotation] -class Item(DefaultDarwin): +class Item(BaseModel): name: str path: str - \ No newline at end of file + + From 5eda4783177329904da6f5cc64172a5120bfc719 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:56:04 +0000 Subject: [PATCH 03/39] base str vs url --- darwin/future/data_objects/darwin.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 196d932a0..f39317c21 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,7 +2,7 @@ from typing import List, Literal, Optional, Union -from pydantic import AnyUrl, BaseModel, validator +from pydantic import BaseModel, validator class Point(BaseModel): @@ -21,6 +21,12 @@ class AnnotationBase(BaseModel): properties: Optional[dict] = None slot_names: Optional[List[str]] = None + @validator('id', always=True) + def id_is_UUID(cls, v: str) -> str: + assert len(v) == 36 + assert '-' in v + return v + class BoundingBox(BaseModel): h: float w: float @@ -61,13 +67,18 @@ class FrameAnnotation(AnnotationBase): ranges: List[int] -AllowedAnnotation = Union[BoundingBoxAnnotation, EllipseAnnotation, PolygonAnnotation] +AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" - schema_ref: AnyUrl + schema_ref: str item: dict annotations: List[AllowedAnnotation] + + @validator('schema_ref', always=True) + def validate_schema_ref(cls, v: str) -> str: + assert v.startswith("http") + return v class Item(BaseModel): From 1b47f2faf3193c80de73060d11fc3d223d80485a Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:46:44 +0000 Subject: [PATCH 04/39] tests for base annotation loading --- .gitignore | 1 + darwin/future/data_objects/darwin.py | 10 +-- darwin/future/tests/data/base_annotation.json | 67 +++++++++++++++++++ .../future/tests/data_objects/test_darwin.py | 38 +++++++++++ 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 darwin/future/tests/data/base_annotation.json create mode 100644 darwin/future/tests/data_objects/test_darwin.py diff --git a/.gitignore b/.gitignore index 3dd50932a..5d4b4611b 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,7 @@ test_output_* !tests/darwin/data/*.png +!darwin/future/tests/data # scripts test.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index f39317c21..0012e7c02 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -9,8 +9,10 @@ class Point(BaseModel): x: float y: float -class PolygonPath(BaseModel): - points: List[Point] +# class PolygonPaths(BaseModel): +# points: List[PolygonPath] + +PolygonPath = List[Point] class Polygon(BaseModel): paths: List[PolygonPath] @@ -22,7 +24,7 @@ class AnnotationBase(BaseModel): slot_names: Optional[List[str]] = None @validator('id', always=True) - def id_is_UUID(cls, v: str) -> str: + def validate_id_is_UUID(cls, v: str) -> str: assert len(v) == 36 assert '-' in v return v @@ -48,7 +50,7 @@ class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - @validator('bounding_box', always=True) + @validator('bounding_box', pre=False) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 diff --git a/darwin/future/tests/data/base_annotation.json b/darwin/future/tests/data/base_annotation.json new file mode 100644 index 000000000..b6b6e2d3c --- /dev/null +++ b/darwin/future/tests/data/base_annotation.json @@ -0,0 +1,67 @@ +{ + "version": "2.0", + "schema_ref": "https://darwin-public.s3.eu-west-1.amazonaws.com/darwin_json/2.0/schema.json", + "item": { + "name": "", + "path": "/" + }, + "annotations": [ + { + "bounding_box": { + "h": 1.0, + "w": 1.0, + "x": 0.0, + "y": 0.0 + }, + "id": "007882ff-99c4-4c6f-b71b-79cfc147fef6", + "name": "test_bb" + }, + { + "ellipse": { + "angle": 0.0, + "center": { + "x": 1.0, + "y": 1.0 + }, + "radius": { + "x": 1.0, + "y": 1.0 + } + }, + "id": "320a60f2-643b-4d74-a117-0ea2fdfe7a61", + "name": "test_ellipse" + }, + { + "bounding_box": { + "h": 1.0, + "w": 1.0, + "x": 0.0, + "y": 0.0 + }, + "id": "012dcc6c-5b77-406b-8cd7-d9567c8b00b7", + "name": "test_poly", + "polygon": { + "paths": [ + [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 1.0, + "y": 0.0 + }, + { + "x": 1.0, + "y": 1.0 + }, + { + "x": 0.0, + "y": 1.0 + } + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py new file mode 100644 index 000000000..d15d4d88c --- /dev/null +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -0,0 +1,38 @@ +import json + +import pytest + +from darwin.future.data_objects.darwin import ( + BoundingBoxAnnotation, + DarwinV2, + EllipseAnnotation, + PolygonAnnotation, +) + + +@pytest.fixture +def raw_json() -> dict: + with open('./darwin/future/tests/data/base_annotation.json') as f: + raw_json = json.load(f) + return raw_json + +def test_loads_base_darwin_v2(raw_json: dict): + test = DarwinV2.parse_obj(raw_json) + assert len(test.annotations) == 3 + assert isinstance(test.annotations[0], BoundingBoxAnnotation) + assert isinstance(test.annotations[1], EllipseAnnotation) + assert isinstance(test.annotations[2], PolygonAnnotation) + + +def test_bbox_annotation(raw_json: dict): + bounds_annotation = raw_json['annotations'][0] + BoundingBoxAnnotation.parse_obj(bounds_annotation) + +def test_ellipse_annotation(raw_json: dict): + ellipse_annotation = raw_json['annotations'][1] + EllipseAnnotation.parse_obj(ellipse_annotation) + +def test_polygon_annotation(raw_json: dict): + polygon_annotation = raw_json['annotations'][2] + PolygonAnnotation.parse_obj(polygon_annotation) + \ No newline at end of file From 7dce8ffdabe0773bc41e68b32e13dd3fa7451a36 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:47:00 +0000 Subject: [PATCH 05/39] cleanup --- darwin/future/data_objects/darwin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 0012e7c02..4373913e4 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -9,9 +9,6 @@ class Point(BaseModel): x: float y: float -# class PolygonPaths(BaseModel): -# points: List[PolygonPath] - PolygonPath = List[Point] class Polygon(BaseModel): From a78fbb04af83b853c7a9625979a45a553a2ae2fb Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:49:04 +0000 Subject: [PATCH 06/39] cleanup 2: electric boogaloo --- darwin/future/data_objects/darwin.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 4373913e4..dcbfa33c9 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -52,10 +52,10 @@ def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> Boundi if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 for point in values['polygon']['paths']: - h = max(h, point.y) - w = max(w, point.x) - x = min(x, point.x) - y = min(y, point.y) + h = max(h, point["y"]) + w = max(w, point["x"]) + x = min(x, point["x"]) + y = min(y, point["y"]) v = BoundingBox(h=h, w=w, x=x, y=y) return v @@ -68,6 +68,10 @@ class FrameAnnotation(AnnotationBase): AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] +class Item(BaseModel): + name: str + path: str + class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: str @@ -79,9 +83,5 @@ def validate_schema_ref(cls, v: str) -> str: assert v.startswith("http") return v - -class Item(BaseModel): - name: str - path: str From fe50af79713b589cf2ab6820dfa5ebfb950bc301 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:56:32 +0000 Subject: [PATCH 07/39] test bounding box auto-calculate --- darwin/future/data_objects/darwin.py | 13 +++++++------ darwin/future/tests/data_objects/test_darwin.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index dcbfa33c9..f5a292fa2 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -47,15 +47,16 @@ class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - @validator('bounding_box', pre=False) + @validator('bounding_box', pre=False, always=True) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 - for point in values['polygon']['paths']: - h = max(h, point["y"]) - w = max(w, point["x"]) - x = min(x, point["x"]) - y = min(y, point["y"]) + for polygon_path in values['polygon'].paths: + for point in polygon_path: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) v = BoundingBox(h=h, w=w, x=x, y=y) return v diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index d15d4d88c..e92482213 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -35,4 +35,15 @@ def test_ellipse_annotation(raw_json: dict): def test_polygon_annotation(raw_json: dict): polygon_annotation = raw_json['annotations'][2] PolygonAnnotation.parse_obj(polygon_annotation) - \ No newline at end of file + + +def test_polygon_bbx_vaidator(raw_json: dict): + polygon_annotation = raw_json['annotations'][2] + without_bbx = polygon_annotation.copy() + del without_bbx['bounding_box'] + without_bb_annotation = PolygonAnnotation.parse_obj(without_bbx) + with_bb_annotation = PolygonAnnotation.parse_obj(polygon_annotation) + + 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 \ No newline at end of file From 1a234488ad0f6cd7f84c4bea2dc1ab21a103e6eb Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:58:18 +0000 Subject: [PATCH 08/39] typing --- darwin/future/data_objects/darwin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index f5a292fa2..8f8605baa 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,6 +2,7 @@ from typing import List, Literal, Optional, Union +from numpy import isin from pydantic import BaseModel, validator @@ -51,8 +52,11 @@ class PolygonAnnotation(AnnotationBase): def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 + assert 'polygon' in values + assert isinstance(values['polygon'], Polygon) for polygon_path in values['polygon'].paths: for point in polygon_path: + assert isinstance(point, Point) h = max(h, point.y) w = max(w, point.x) x = min(x, point.x) From bd9f0351eccd92ae9f45d0f6dfacb493a3683082 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:58:46 +0000 Subject: [PATCH 09/39] cleanup --- darwin/future/data_objects/darwin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 8f8605baa..9045af732 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,7 +2,6 @@ from typing import List, Literal, Optional, Union -from numpy import isin from pydantic import BaseModel, validator From ffa0b281a107b5ce6d5bc9a1a7820a26f3d78778 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 13:38:39 +0000 Subject: [PATCH 10/39] property addition --- darwin/future/data_objects/properties.py | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 darwin/future/data_objects/properties.py diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py new file mode 100644 index 000000000..69f41714e --- /dev/null +++ b/darwin/future/data_objects/properties.py @@ -0,0 +1,83 @@ +from re import sub +from typing import List, Optional + +from pydantic import validator + +from darwin.future.data_objects.pydantic_base import DefaultDarwin + + +class PropertyOption(DefaultDarwin): + """ + Describes a single option for a property + + Attributes: + value (str): Value of the option + color (Optional[str]): Color of the option + type (Optional[str]): Type of the option + + Validators: + color (validator): Validates that the color is in rgba format + """ + + value: str + color: str + type: str + + @validator("color") + def validate_rgba(cls, v: str) -> Optional[str]: + if v is not None: + if not v.startswith("rgba"): + raise ValueError("Color must be in rgba format") + return v + +class FullProperty(DefaultDarwin): + """ + Describes the property and all of the potential options that are associated with it + + Attributes: + name (str): Name of the property + type (str): Type of the property + required (bool): If the property is required + options (List[PropertyOption]): List of all options for the property + """ + name: str + type: str + required: bool + options: List[PropertyOption] + +class PropertyClass(DefaultDarwin): + """ + Metadata.json -> property mapping. Contains all properties for a class contained + in the metadata.json file. Along with all options for each property that is associated + with the class. + + Attributes: + name (str): Name of the class + type (str): Type of the class + description (Optional[str]): Description of the class + color (Optional[str]): Color of the class in the UI + sub_types (Optional[List[str]]): Sub types of the class + properties (List[FullProperty]): List of all properties for the class with all options + """ + name: str + type: str + description: Optional[str] + color: Optional[str] + sub_types: Optional[List[str]] + properties: List[FullProperty] + + +class AnnotationProperty(DefaultDarwin): + """ + Selected property for an annotation found inside a darwin annotation + + Attributes: + frame_index (int): Frame index of the annotation + name (str): Name of the property + type (str): Type of the property + value (str): Value of the property + """ + frame_index: int + name: str + type: str + value: str \ No newline at end of file From 153960a6c6dab6a9f76e1a97b42dc8beba072713 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 14:07:07 +0000 Subject: [PATCH 11/39] property metadata parsing --- darwin/future/data_objects/darwin.py | 44 ++++++++++----- darwin/future/data_objects/properties.py | 55 +++++++++++++------ darwin/future/data_objects/property.py | 0 .../future/tests/data_objects/test_darwin.py | 25 +++++---- darwin/importer/formats/csv_tags_video.py | 4 +- 5 files changed, 84 insertions(+), 44 deletions(-) delete mode 100644 darwin/future/data_objects/property.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 9045af732..0d0f5135a 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -4,56 +4,68 @@ from pydantic import BaseModel, validator +from darwin.future.data_objects.properties import SelectedProperty + class Point(BaseModel): x: float y: float + PolygonPath = List[Point] + class Polygon(BaseModel): paths: List[PolygonPath] + class AnnotationBase(BaseModel): id: str name: str - properties: Optional[dict] = None + properties: Optional[SelectedProperty] = None slot_names: Optional[List[str]] = None - @validator('id', always=True) + @validator("id", always=True) def validate_id_is_UUID(cls, v: str) -> str: assert len(v) == 36 - assert '-' in v + assert "-" in v return v + class BoundingBox(BaseModel): h: float w: float x: float y: float + class BoundingBoxAnnotation(AnnotationBase): bounding_box: BoundingBox + class Ellipse(BaseModel): center: Point radius: Point angle: float + class EllipseAnnotation(AnnotationBase): ellipse: Ellipse + class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - - @validator('bounding_box', pre=False, always=True) - def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: + + @validator("bounding_box", pre=False, always=True) + def validate_bounding_box( + cls, v: Optional[BoundingBox], values: dict + ) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 - assert 'polygon' in values - assert isinstance(values['polygon'], Polygon) - for polygon_path in values['polygon'].paths: + assert "polygon" in values + assert isinstance(values["polygon"], Polygon) + for polygon_path in values["polygon"].paths: for point in polygon_path: assert isinstance(point, Point) h = max(h, point.y) @@ -63,6 +75,7 @@ def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> Boundi v = BoundingBox(h=h, w=w, x=x, y=y) return v + class FrameAnnotation(AnnotationBase): frames: List interpolated: bool @@ -70,22 +83,23 @@ class FrameAnnotation(AnnotationBase): ranges: List[int] -AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] +AllowedAnnotation = Union[ + PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation +] + class Item(BaseModel): name: str path: str + class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: str item: dict annotations: List[AllowedAnnotation] - - @validator('schema_ref', always=True) + + @validator("schema_ref", always=True) def validate_schema_ref(cls, v: str) -> str: assert v.startswith("http") return v - - - diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py index 69f41714e..3507685a8 100644 --- a/darwin/future/data_objects/properties.py +++ b/darwin/future/data_objects/properties.py @@ -1,4 +1,8 @@ -from re import sub +from __future__ import annotations + +import json +import os +from pathlib import Path from typing import List, Optional from pydantic import validator @@ -9,48 +13,51 @@ class PropertyOption(DefaultDarwin): """ Describes a single option for a property - + Attributes: value (str): Value of the option color (Optional[str]): Color of the option type (Optional[str]): Type of the option - + Validators: color (validator): Validates that the color is in rgba format """ - + value: str color: str type: str - + @validator("color") def validate_rgba(cls, v: str) -> Optional[str]: if v is not None: if not v.startswith("rgba"): raise ValueError("Color must be in rgba format") return v - + + class FullProperty(DefaultDarwin): """ Describes the property and all of the potential options that are associated with it - + Attributes: name (str): Name of the property type (str): Type of the property required (bool): If the property is required options (List[PropertyOption]): List of all options for the property """ + name: str type: str required: bool options: List[PropertyOption] -class PropertyClass(DefaultDarwin): - """ + +class MetaDataClass(DefaultDarwin): + """ Metadata.json -> property mapping. Contains all properties for a class contained in the metadata.json file. Along with all options for each property that is associated - with the class. - + with the class. + Attributes: name (str): Name of the class type (str): Type of the class @@ -59,6 +66,7 @@ class PropertyClass(DefaultDarwin): sub_types (Optional[List[str]]): Sub types of the class properties (List[FullProperty]): List of all properties for the class with all options """ + name: str type: str description: Optional[str] @@ -66,11 +74,26 @@ class PropertyClass(DefaultDarwin): sub_types: Optional[List[str]] properties: List[FullProperty] - -class AnnotationProperty(DefaultDarwin): - """ + @classmethod + def from_path(cls, path: Path) -> List[MetaDataClass]: + if not path.exists(): + raise FileNotFoundError(f"File {path} does not exist") + if os.path.isdir(path): + if os.path.exists(path / ".v7" / "metadata.json"): + path = path / ".v7" / "metadata.json" + else: + raise FileNotFoundError(f"File metadata.json does not exist in path") + if path.suffix != ".json": + raise ValueError(f"File {path} must be a json file") + with open(path, "r") as f: + data = json.load(f) + return [cls(**d) for d in data["classes"]] + + +class SelectedProperty(DefaultDarwin): + """ Selected property for an annotation found inside a darwin annotation - + Attributes: frame_index (int): Frame index of the annotation name (str): Name of the property @@ -80,4 +103,4 @@ class AnnotationProperty(DefaultDarwin): frame_index: int name: str type: str - value: str \ No newline at end of file + value: str diff --git a/darwin/future/data_objects/property.py b/darwin/future/data_objects/property.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index e92482213..11c21e32a 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -12,11 +12,12 @@ @pytest.fixture def raw_json() -> dict: - with open('./darwin/future/tests/data/base_annotation.json') as f: + with open("./darwin/future/tests/data/base_annotation.json") as f: raw_json = json.load(f) return raw_json -def test_loads_base_darwin_v2(raw_json: dict): + +def test_loads_base_darwin_v2(raw_json: dict): test = DarwinV2.parse_obj(raw_json) assert len(test.annotations) == 3 assert isinstance(test.annotations[0], BoundingBoxAnnotation) @@ -25,25 +26,27 @@ def test_loads_base_darwin_v2(raw_json: dict): def test_bbox_annotation(raw_json: dict): - bounds_annotation = raw_json['annotations'][0] + bounds_annotation = raw_json["annotations"][0] BoundingBoxAnnotation.parse_obj(bounds_annotation) + def test_ellipse_annotation(raw_json: dict): - ellipse_annotation = raw_json['annotations'][1] + ellipse_annotation = raw_json["annotations"][1] EllipseAnnotation.parse_obj(ellipse_annotation) + def test_polygon_annotation(raw_json: dict): - polygon_annotation = raw_json['annotations'][2] + polygon_annotation = raw_json["annotations"][2] PolygonAnnotation.parse_obj(polygon_annotation) - - + + def test_polygon_bbx_vaidator(raw_json: dict): - polygon_annotation = raw_json['annotations'][2] + polygon_annotation = raw_json["annotations"][2] without_bbx = polygon_annotation.copy() - del without_bbx['bounding_box'] + del without_bbx["bounding_box"] without_bb_annotation = PolygonAnnotation.parse_obj(without_bbx) with_bb_annotation = PolygonAnnotation.parse_obj(polygon_annotation) - + 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 \ No newline at end of file + assert without_bb_annotation == with_bb_annotation diff --git a/darwin/importer/formats/csv_tags_video.py b/darwin/importer/formats/csv_tags_video.py index d972653cd..a6885ac0c 100644 --- a/darwin/importer/formats/csv_tags_video.py +++ b/darwin/importer/formats/csv_tags_video.py @@ -51,9 +51,9 @@ def parse_path(path: Path) -> Optional[List[dt.AnnotationFile]]: file_annotation_map[filename].append(annotation) for filename in file_annotation_map: annotations = file_annotation_map[filename] - annotation_classes = set( + annotation_classes = { annotation.annotation_class for annotation in annotations - ) + } filename_path = Path(filename) remote_path = str(filename_path.parent) if not remote_path.startswith("/"): From 368049061129b5f872367f8fdeff776901753cc1 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 15:15:09 +0000 Subject: [PATCH 12/39] cleanup validator --- darwin/future/data_objects/darwin.py | 38 ++++++++++++++++++------ darwin/future/data_objects/properties.py | 12 ++++---- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 0d0f5135a..c04143bbd 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -10,6 +10,12 @@ class Point(BaseModel): x: float y: float + + def __add__(self, other: Point) -> Point: + return Point(x=self.x + other.x, y=self.y + other.y) + + def __sub__(self, other: Point) -> Point: + return Point(x=self.x - other.x, y=self.y - other.y) PolygonPath = List[Point] @@ -17,6 +23,24 @@ class Point(BaseModel): class Polygon(BaseModel): paths: List[PolygonPath] + + def bounding_box(self) -> BoundingBox: + h, w, x, y = 0.0, 0.0, 0.0, 0.0 + for polygon_path in self.paths: + for point in polygon_path: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) + return BoundingBox(h=h, w=w, x=x, y=y) + + @property + def is_complex(self) -> bool: + return len(self.paths) > 1 + + @property + def center(self) -> Point: + return self.bounding_box().center class AnnotationBase(BaseModel): @@ -37,6 +61,10 @@ class BoundingBox(BaseModel): w: float x: float y: float + + @property + def center(self) -> Point: + return Point(x=self.x + self.w / 2, y=self.y + self.h / 2) class BoundingBoxAnnotation(AnnotationBase): @@ -62,17 +90,9 @@ def validate_bounding_box( cls, v: Optional[BoundingBox], values: dict ) -> BoundingBox: if v is None: - h, w, x, y = 0.0, 0.0, 0.0, 0.0 assert "polygon" in values assert isinstance(values["polygon"], Polygon) - for polygon_path in values["polygon"].paths: - for point in polygon_path: - assert isinstance(point, Point) - h = max(h, point.y) - w = max(w, point.x) - x = min(x, point.x) - y = min(y, point.y) - v = BoundingBox(h=h, w=w, x=x, y=y) + v = values["polygon"].bounding_box() return v diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py index 3507685a8..633c2db4f 100644 --- a/darwin/future/data_objects/properties.py +++ b/darwin/future/data_objects/properties.py @@ -28,10 +28,9 @@ class PropertyOption(DefaultDarwin): type: str @validator("color") - def validate_rgba(cls, v: str) -> Optional[str]: - if v is not None: - if not v.startswith("rgba"): - raise ValueError("Color must be in rgba format") + def validate_rgba(cls, v: str) -> str: + if not v.startswith("rgba"): + raise ValueError("Color must be in rgba format") return v @@ -73,7 +72,7 @@ class MetaDataClass(DefaultDarwin): color: Optional[str] sub_types: Optional[List[str]] properties: List[FullProperty] - + @classmethod def from_path(cls, path: Path) -> List[MetaDataClass]: if not path.exists(): @@ -88,7 +87,7 @@ def from_path(cls, path: Path) -> List[MetaDataClass]: with open(path, "r") as f: data = json.load(f) return [cls(**d) for d in data["classes"]] - + class SelectedProperty(DefaultDarwin): """ @@ -100,6 +99,7 @@ class SelectedProperty(DefaultDarwin): type (str): Type of the property value (str): Value of the property """ + frame_index: int name: str type: str From 581d63e36c67e0afa43616896e44fdb4442380af Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 15:31:16 +0000 Subject: [PATCH 13/39] test for properties base example --- darwin/future/data_objects/darwin.py | 12 ++-- darwin/future/tests/data/.v7/metadata.json | 63 +++++++++++++++++++ .../tests/data_objects/test_properties.py | 37 +++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 darwin/future/tests/data/.v7/metadata.json create mode 100644 darwin/future/tests/data_objects/test_properties.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index c04143bbd..2e4d334b7 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -10,10 +10,10 @@ class Point(BaseModel): x: float y: float - + def __add__(self, other: Point) -> Point: return Point(x=self.x + other.x, y=self.y + other.y) - + def __sub__(self, other: Point) -> Point: return Point(x=self.x - other.x, y=self.y - other.y) @@ -23,7 +23,7 @@ def __sub__(self, other: Point) -> Point: class Polygon(BaseModel): paths: List[PolygonPath] - + def bounding_box(self) -> BoundingBox: h, w, x, y = 0.0, 0.0, 0.0, 0.0 for polygon_path in self.paths: @@ -33,11 +33,11 @@ def bounding_box(self) -> BoundingBox: x = min(x, point.x) y = min(y, point.y) return BoundingBox(h=h, w=w, x=x, y=y) - + @property def is_complex(self) -> bool: return len(self.paths) > 1 - + @property def center(self) -> Point: return self.bounding_box().center @@ -61,7 +61,7 @@ class BoundingBox(BaseModel): w: float x: float y: float - + @property def center(self) -> Point: return Point(x=self.x + self.w / 2, y=self.y + self.h / 2) diff --git a/darwin/future/tests/data/.v7/metadata.json b/darwin/future/tests/data/.v7/metadata.json new file mode 100644 index 000000000..561711c61 --- /dev/null +++ b/darwin/future/tests/data/.v7/metadata.json @@ -0,0 +1,63 @@ +{ + "classes": [ + { + "name": "Test bounding box", + "type": "bounding_box", + "description": null, + "color": "rgba(255,145,82,1)", + "sub_types": [ + "inference" + ], + "properties": [] + }, + { + "name": "Test Polygon", + "type": "polygon", + "description": null, + "color": "rgba(219,255,0,1.0)", + "sub_types": [ + "directional_vector", + "attributes", + "text", + "instance_id", + "inference" + ], + "properties": [ + { + "name": "Property 1", + "type": "multi_select", + "options": [ + { + "type": "string", + "value": "first value", + "color": "rgba(255,92,0,1.0)" + }, + { + "type": "string", + "value": "second value", + "color": "rgba(0,0,0,1.0)" + } + ], + "required": false + }, + { + "name": "Property 2", + "type": "single_select", + "options": [ + { + "type": "string", + "value": "first value", + "color": "rgba(0,194,255,1.0)" + }, + { + "type": "string", + "value": "second value", + "color": "rgba(255,255,255,1.0)" + } + ], + "required": false + } + ] + } + ] +} \ No newline at end of file diff --git a/darwin/future/tests/data_objects/test_properties.py b/darwin/future/tests/data_objects/test_properties.py new file mode 100644 index 000000000..f924bcc4f --- /dev/null +++ b/darwin/future/tests/data_objects/test_properties.py @@ -0,0 +1,37 @@ +from os import path +from pathlib import Path + +import pytest + +from darwin.future.data_objects.properties import MetaDataClass + + +@pytest.fixture +def path_to_metadata_folder() -> Path: + return Path("darwin/future/tests/data") + +@pytest.fixture +def path_to_metadata(path_to_metadata_folder: Path) -> Path: + return path_to_metadata_folder / ".v7" / "metadata.json" + + +def test_properties_metadata_loads_folder(path_to_metadata: Path) -> None: + metadata = MetaDataClass.from_path(path_to_metadata) + assert metadata is not None + assert len(metadata) == 2 + + +def test_properties_metadata_loads_file(path_to_metadata: Path) -> None: + metadata = MetaDataClass.from_path(path_to_metadata) + assert metadata is not None + assert len(metadata) == 2 + + +def test_properties_metadata_fails() -> None: + path = Path("darwin/future/tests/data/does_not_exist.json") + with pytest.raises(FileNotFoundError): + MetaDataClass.from_path(path) + + path = Path("darwin/future/tests/data/does_not_exist") + with pytest.raises(FileNotFoundError): + MetaDataClass.from_path(path) From 87074d58898ebf16bcdf7adfbea8e0f04509501f Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 15:46:16 +0000 Subject: [PATCH 14/39] linting --- darwin/future/data_objects/properties.py | 2 +- darwin/future/tests/data_objects/test_properties.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py index 633c2db4f..e7ac73aaf 100644 --- a/darwin/future/data_objects/properties.py +++ b/darwin/future/data_objects/properties.py @@ -81,7 +81,7 @@ def from_path(cls, path: Path) -> List[MetaDataClass]: if os.path.exists(path / ".v7" / "metadata.json"): path = path / ".v7" / "metadata.json" else: - raise FileNotFoundError(f"File metadata.json does not exist in path") + raise FileNotFoundError("File metadata.json does not exist in path") if path.suffix != ".json": raise ValueError(f"File {path} must be a json file") with open(path, "r") as f: diff --git a/darwin/future/tests/data_objects/test_properties.py b/darwin/future/tests/data_objects/test_properties.py index f924bcc4f..36c570c61 100644 --- a/darwin/future/tests/data_objects/test_properties.py +++ b/darwin/future/tests/data_objects/test_properties.py @@ -1,4 +1,3 @@ -from os import path from pathlib import Path import pytest @@ -10,6 +9,7 @@ def path_to_metadata_folder() -> Path: return Path("darwin/future/tests/data") + @pytest.fixture def path_to_metadata(path_to_metadata_folder: Path) -> Path: return path_to_metadata_folder / ".v7" / "metadata.json" From 802c217baf537ec12827181f73a57bd06a0acb47 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 11 Dec 2023 22:16:31 +0000 Subject: [PATCH 15/39] Changes for list/get endpoint --- .../data_objects/{darwin.py => darwinV2.py} | 0 darwin/future/data_objects/properties.py | 18 ++++++++++++------ .../future/tests/data_objects/test_darwin.py | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) rename darwin/future/data_objects/{darwin.py => darwinV2.py} (100%) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwinV2.py similarity index 100% rename from darwin/future/data_objects/darwin.py rename to darwin/future/data_objects/darwinV2.py diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py index e7ac73aaf..6a4342624 100644 --- a/darwin/future/data_objects/properties.py +++ b/darwin/future/data_objects/properties.py @@ -3,7 +3,7 @@ import json import os from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional, Union from pydantic import validator @@ -22,10 +22,11 @@ class PropertyOption(DefaultDarwin): Validators: color (validator): Validates that the color is in rgba format """ - - value: str - color: str + id: Optional[str] + position: Optional[int] type: str + value: Union[Dict[str, str], str] + color: str @validator("color") def validate_rgba(cls, v: str) -> str: @@ -44,11 +45,16 @@ class FullProperty(DefaultDarwin): required (bool): If the property is required options (List[PropertyOption]): List of all options for the property """ - + id: Optional[str] name: str type: str + description: Optional[str] required: bool - options: List[PropertyOption] + slug: Optional[str] + team_id: Optional[int] + annotation_class_id: Optional[int] + property_values: Optional[List[PropertyOption]] + options: Optional[List[PropertyOption]] class MetaDataClass(DefaultDarwin): diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index 11c21e32a..cda91ad10 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -2,7 +2,7 @@ import pytest -from darwin.future.data_objects.darwin import ( +from darwin.future.data_objects.darwinV2 import ( BoundingBoxAnnotation, DarwinV2, EllipseAnnotation, From 6decf440d949b0e19ea540ac012ec3adfae731f8 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 11 Dec 2023 22:26:16 +0000 Subject: [PATCH 16/39] linting --- darwin/future/data_objects/properties.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/darwin/future/data_objects/properties.py b/darwin/future/data_objects/properties.py index 6a4342624..3e9443f5f 100644 --- a/darwin/future/data_objects/properties.py +++ b/darwin/future/data_objects/properties.py @@ -22,6 +22,7 @@ class PropertyOption(DefaultDarwin): Validators: color (validator): Validates that the color is in rgba format """ + id: Optional[str] position: Optional[int] type: str @@ -45,6 +46,7 @@ class FullProperty(DefaultDarwin): required (bool): If the property is required options (List[PropertyOption]): List of all options for the property """ + id: Optional[str] name: str type: str From b000467407e67daa5512f5555007c19732900ba8 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 11 Dec 2023 23:20:30 +0000 Subject: [PATCH 17/39] get_all_properties --- darwin/future/core/properties/__init__.py | 0 darwin/future/core/properties/get.py | 30 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 darwin/future/core/properties/__init__.py create mode 100644 darwin/future/core/properties/get.py diff --git a/darwin/future/core/properties/__init__.py b/darwin/future/core/properties/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/darwin/future/core/properties/get.py b/darwin/future/core/properties/get.py new file mode 100644 index 000000000..15eab55a4 --- /dev/null +++ b/darwin/future/core/properties/get.py @@ -0,0 +1,30 @@ +from typing import List, Optional + +from pydantic import parse_obj_as + +from darwin.future.core.client import ClientCore +from darwin.future.data_objects.properties import FullProperty + + +def get_all_properties( + client: ClientCore, team_slug: Optional[str] = None +) -> List[FullProperty]: + """ + Returns a TeamCore 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: + TeamCore: The TeamCore object 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") + assert isinstance(response, dict) + return parse_obj_as(List[FullProperty], response.get("properties")) From 79654801458b3d65157a09bb6bd27a2e9e6987d1 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 21:40:10 +0000 Subject: [PATCH 18/39] get properties and get by id --- darwin/future/core/properties/__init__.py | 5 ++ darwin/future/core/properties/get.py | 54 ++++++++++-- darwin/future/tests/core/fixtures.py | 26 ++++++ .../future/tests/core/properties/__init__.py | 0 .../future/tests/core/properties/test_get.py | 82 +++++++++++++++++++ 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 darwin/future/tests/core/properties/__init__.py create mode 100644 darwin/future/tests/core/properties/test_get.py diff --git a/darwin/future/core/properties/__init__.py b/darwin/future/core/properties/__init__.py index e69de29bb..ef53ea047 100644 --- a/darwin/future/core/properties/__init__.py +++ 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 index 15eab55a4..2f1637a6c 100644 --- a/darwin/future/core/properties/get.py +++ b/darwin/future/core/properties/get.py @@ -1,16 +1,20 @@ -from typing import List, Optional +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_all_properties( - client: ClientCore, team_slug: Optional[str] = None +def get_team_properties( + client: ClientCore, + team_slug: Optional[str] = None, + params: Optional[QueryString] = None, ) -> List[FullProperty]: """ - Returns a TeamCore object for the specified team slug. + Returns a List[FullProperty] object for the specified team slug. Parameters: client (ClientCore): The client to use for the request. @@ -18,13 +22,51 @@ def get_all_properties( default team from the client's config will be used. Returns: - TeamCore: The TeamCore object for the specified team slug. + 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") + 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 + 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/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 210042929..8a6c33d0c 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -8,10 +8,36 @@ 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 +@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..5c888051e --- /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 \ No newline at end of file From c79d8aaf1f7088ee6e095a0c26299cb0950d79ab Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 21:54:57 +0000 Subject: [PATCH 19/39] linting --- darwin/future/tests/core/fixtures.py | 2 ++ darwin/future/tests/core/properties/test_get.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/darwin/future/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 4623e7c29..8c7434416 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -24,6 +24,7 @@ def base_property_option() -> PropertyOption: color="rgba(0,0,0,0)", ) + @pytest.fixture def base_property_object(base_property_option: PropertyOption) -> FullProperty: return FullProperty( @@ -39,6 +40,7 @@ def base_property_object(base_property_option: PropertyOption) -> FullProperty: options=[base_property_option], ) + @pytest.fixture def base_config() -> DarwinConfig: return DarwinConfig( diff --git a/darwin/future/tests/core/properties/test_get.py b/darwin/future/tests/core/properties/test_get.py index 5c888051e..98c0d5609 100644 --- a/darwin/future/tests/core/properties/test_get.py +++ b/darwin/future/tests/core/properties/test_get.py @@ -24,7 +24,7 @@ def test_get_team_properties( responses.GET, f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties", json=response_data, - status=200 + status=200, ) # Call the function being tested @@ -49,7 +49,7 @@ def test_get_team_full_properties( query_param_matcher({"include_values": "True"}), ], json=response_data, - status=200 + status=200, ) params = QueryString({"include_values": True}) # Call the function being tested @@ -79,4 +79,4 @@ def test_get_property_by_id( # Assertions assert isinstance(property, FullProperty) - assert property == base_property_object \ No newline at end of file + assert property == base_property_object From 7865e11ab01ac0147ac054f9425f1c2e5a1f2496 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:18:11 +0000 Subject: [PATCH 20/39] base annotation darwin --- darwin/future/data_objects/darwin.py | 67 ++++++++++++++++++++++++++ darwin/future/data_objects/property.py | 0 2 files changed, 67 insertions(+) create mode 100644 darwin/future/data_objects/darwin.py create mode 100644 darwin/future/data_objects/property.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py new file mode 100644 index 000000000..35032b7c0 --- /dev/null +++ b/darwin/future/data_objects/darwin.py @@ -0,0 +1,67 @@ +from typing import List, Literal, Optional, Union + +from pydantic import AnyUrl, validator + +from darwin.future.pydantic_base import DefaultDarwin + + +class Point(DefaultDarwin): + x: float + y: float + +class Polygon(DefaultDarwin): + paths: list[list[Point]] + +class AnnotationBase(DefaultDarwin): + id: str + name: str + properties: Optional[dict] = None + slot_names: Optional[List[str]] = None + +class BoundingBox(DefaultDarwin): + h: float + w: float + x: float + y: float + +class BoundingBoxAnnotation(AnnotationBase): + bouding_box: BoundingBox + +class Ellipse(DefaultDarwin): + center: Point + radius: Point + angle: float + +class EllipseAnnotation(AnnotationBase): + ellipse: Ellipse + +class PolygonAnnotation(AnnotationBase): + polygon: Polygon + bounding_box: Optional[BoundingBox] = None + + @validator('bounding_box', always=True) + def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: + if v is None: + raise NotImplementedError("TODO: Implement bounding box from polygon") + return v + +class FrameAnnotation(AnnotationBase): + frames: list + interpolated: bool + interpolate_algorithm: str + ranges: list[int] + + +AllowedAnnotation = Union[BoundingBoxAnnotation, FrameAnnotation, EllipseAnnotation, PolygonAnnotation] + +class DarwinV2(DefaultDarwin): + version: Literal["2.0"] = "2.0" + schema_ref: AnyUrl + item: dict + annotations: List[AllowedAnnotation] + + +class Item(DefaultDarwin): + name: str + path: str + \ No newline at end of file diff --git a/darwin/future/data_objects/property.py b/darwin/future/data_objects/property.py new file mode 100644 index 000000000..e69de29bb From a9dcbb1ae85c242b94a49b1437cadd4cad20d0a7 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:42:26 +0000 Subject: [PATCH 21/39] fixes for annotations --- darwin/future/data_objects/darwin.py | 44 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 35032b7c0..196d932a0 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -1,33 +1,36 @@ -from typing import List, Literal, Optional, Union +from __future__ import annotations -from pydantic import AnyUrl, validator +from typing import List, Literal, Optional, Union -from darwin.future.pydantic_base import DefaultDarwin +from pydantic import AnyUrl, BaseModel, validator -class Point(DefaultDarwin): +class Point(BaseModel): x: float y: float -class Polygon(DefaultDarwin): - paths: list[list[Point]] +class PolygonPath(BaseModel): + points: List[Point] + +class Polygon(BaseModel): + paths: List[PolygonPath] -class AnnotationBase(DefaultDarwin): +class AnnotationBase(BaseModel): id: str name: str properties: Optional[dict] = None slot_names: Optional[List[str]] = None -class BoundingBox(DefaultDarwin): +class BoundingBox(BaseModel): h: float w: float x: float y: float class BoundingBoxAnnotation(AnnotationBase): - bouding_box: BoundingBox + bounding_box: BoundingBox -class Ellipse(DefaultDarwin): +class Ellipse(BaseModel): center: Point radius: Point angle: float @@ -42,26 +45,33 @@ class PolygonAnnotation(AnnotationBase): @validator('bounding_box', always=True) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: - raise NotImplementedError("TODO: Implement bounding box from polygon") + h, w, x, y = 0.0, 0.0, 0.0, 0.0 + for point in values['polygon']['paths']: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) + v = BoundingBox(h=h, w=w, x=x, y=y) return v class FrameAnnotation(AnnotationBase): - frames: list + frames: List interpolated: bool interpolate_algorithm: str - ranges: list[int] + ranges: List[int] -AllowedAnnotation = Union[BoundingBoxAnnotation, FrameAnnotation, EllipseAnnotation, PolygonAnnotation] +AllowedAnnotation = Union[BoundingBoxAnnotation, EllipseAnnotation, PolygonAnnotation] -class DarwinV2(DefaultDarwin): +class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: AnyUrl item: dict annotations: List[AllowedAnnotation] -class Item(DefaultDarwin): +class Item(BaseModel): name: str path: str - \ No newline at end of file + + From 62e841fb026e172df6e5c4543777c5979e30a1ed Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 22:56:04 +0000 Subject: [PATCH 22/39] base str vs url --- darwin/future/data_objects/darwin.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 196d932a0..f39317c21 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,7 +2,7 @@ from typing import List, Literal, Optional, Union -from pydantic import AnyUrl, BaseModel, validator +from pydantic import BaseModel, validator class Point(BaseModel): @@ -21,6 +21,12 @@ class AnnotationBase(BaseModel): properties: Optional[dict] = None slot_names: Optional[List[str]] = None + @validator('id', always=True) + def id_is_UUID(cls, v: str) -> str: + assert len(v) == 36 + assert '-' in v + return v + class BoundingBox(BaseModel): h: float w: float @@ -61,13 +67,18 @@ class FrameAnnotation(AnnotationBase): ranges: List[int] -AllowedAnnotation = Union[BoundingBoxAnnotation, EllipseAnnotation, PolygonAnnotation] +AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" - schema_ref: AnyUrl + schema_ref: str item: dict annotations: List[AllowedAnnotation] + + @validator('schema_ref', always=True) + def validate_schema_ref(cls, v: str) -> str: + assert v.startswith("http") + return v class Item(BaseModel): From 11e193b93bfa006127e69d9eeecbe4e3bacd8e3e Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:46:44 +0000 Subject: [PATCH 23/39] tests for base annotation loading --- darwin/future/data_objects/darwin.py | 10 ++++++---- darwin/future/tests/data_objects/test_darwin.py | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index f39317c21..0012e7c02 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -9,8 +9,10 @@ class Point(BaseModel): x: float y: float -class PolygonPath(BaseModel): - points: List[Point] +# class PolygonPaths(BaseModel): +# points: List[PolygonPath] + +PolygonPath = List[Point] class Polygon(BaseModel): paths: List[PolygonPath] @@ -22,7 +24,7 @@ class AnnotationBase(BaseModel): slot_names: Optional[List[str]] = None @validator('id', always=True) - def id_is_UUID(cls, v: str) -> str: + def validate_id_is_UUID(cls, v: str) -> str: assert len(v) == 36 assert '-' in v return v @@ -48,7 +50,7 @@ class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - @validator('bounding_box', always=True) + @validator('bounding_box', pre=False) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index cda91ad10..638d56a07 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -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) From 780b0e848d212338ce651dec25c0c5fd6b009efa Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:47:00 +0000 Subject: [PATCH 24/39] cleanup --- darwin/future/data_objects/darwin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 0012e7c02..4373913e4 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -9,9 +9,6 @@ class Point(BaseModel): x: float y: float -# class PolygonPaths(BaseModel): -# points: List[PolygonPath] - PolygonPath = List[Point] class Polygon(BaseModel): From 48b26a8edec6abb785a29059cf7958cb012a9343 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:49:04 +0000 Subject: [PATCH 25/39] cleanup 2: electric boogaloo --- darwin/future/data_objects/darwin.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 4373913e4..dcbfa33c9 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -52,10 +52,10 @@ def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> Boundi if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 for point in values['polygon']['paths']: - h = max(h, point.y) - w = max(w, point.x) - x = min(x, point.x) - y = min(y, point.y) + h = max(h, point["y"]) + w = max(w, point["x"]) + x = min(x, point["x"]) + y = min(y, point["y"]) v = BoundingBox(h=h, w=w, x=x, y=y) return v @@ -68,6 +68,10 @@ class FrameAnnotation(AnnotationBase): AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] +class Item(BaseModel): + name: str + path: str + class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: str @@ -79,9 +83,5 @@ def validate_schema_ref(cls, v: str) -> str: assert v.startswith("http") return v - -class Item(BaseModel): - name: str - path: str From 05b057a4d2b413b5470c5cdbc6fd76f1983df3ff Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:56:32 +0000 Subject: [PATCH 26/39] test bounding box auto-calculate --- darwin/future/data_objects/darwin.py | 13 +++++++------ darwin/future/tests/data_objects/test_darwin.py | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index dcbfa33c9..f5a292fa2 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -47,15 +47,16 @@ class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - @validator('bounding_box', pre=False) + @validator('bounding_box', pre=False, always=True) def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 - for point in values['polygon']['paths']: - h = max(h, point["y"]) - w = max(w, point["x"]) - x = min(x, point["x"]) - y = min(y, point["y"]) + for polygon_path in values['polygon'].paths: + for point in polygon_path: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) v = BoundingBox(h=h, w=w, x=x, y=y) return v diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index 638d56a07..be75131ef 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -39,8 +39,7 @@ def test_polygon_annotation(raw_json: dict): polygon_annotation = raw_json["annotations"][2] 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"] From f5a795ad772369cf0fa93f595406f06fb292be7e Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:58:18 +0000 Subject: [PATCH 27/39] typing --- darwin/future/data_objects/darwin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index f5a292fa2..8f8605baa 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,6 +2,7 @@ from typing import List, Literal, Optional, Union +from numpy import isin from pydantic import BaseModel, validator @@ -51,8 +52,11 @@ class PolygonAnnotation(AnnotationBase): def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 + assert 'polygon' in values + assert isinstance(values['polygon'], Polygon) for polygon_path in values['polygon'].paths: for point in polygon_path: + assert isinstance(point, Point) h = max(h, point.y) w = max(w, point.x) x = min(x, point.x) From 7aae4913ca8c4c25031e36118f3332bb22423d74 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Thu, 7 Dec 2023 23:58:46 +0000 Subject: [PATCH 28/39] cleanup --- darwin/future/data_objects/darwin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 8f8605baa..9045af732 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -2,7 +2,6 @@ from typing import List, Literal, Optional, Union -from numpy import isin from pydantic import BaseModel, validator From 8a32881d3a8b5f9d2032527eab6073a7f32d0b48 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 14:07:07 +0000 Subject: [PATCH 29/39] property metadata parsing --- darwin/future/data_objects/darwin.py | 44 +++++++++++++++++--------- darwin/future/data_objects/property.py | 0 2 files changed, 29 insertions(+), 15 deletions(-) delete mode 100644 darwin/future/data_objects/property.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 9045af732..0d0f5135a 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -4,56 +4,68 @@ from pydantic import BaseModel, validator +from darwin.future.data_objects.properties import SelectedProperty + class Point(BaseModel): x: float y: float + PolygonPath = List[Point] + class Polygon(BaseModel): paths: List[PolygonPath] + class AnnotationBase(BaseModel): id: str name: str - properties: Optional[dict] = None + properties: Optional[SelectedProperty] = None slot_names: Optional[List[str]] = None - @validator('id', always=True) + @validator("id", always=True) def validate_id_is_UUID(cls, v: str) -> str: assert len(v) == 36 - assert '-' in v + assert "-" in v return v + class BoundingBox(BaseModel): h: float w: float x: float y: float + class BoundingBoxAnnotation(AnnotationBase): bounding_box: BoundingBox + class Ellipse(BaseModel): center: Point radius: Point angle: float + class EllipseAnnotation(AnnotationBase): ellipse: Ellipse + class PolygonAnnotation(AnnotationBase): polygon: Polygon bounding_box: Optional[BoundingBox] = None - - @validator('bounding_box', pre=False, always=True) - def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> BoundingBox: + + @validator("bounding_box", pre=False, always=True) + def validate_bounding_box( + cls, v: Optional[BoundingBox], values: dict + ) -> BoundingBox: if v is None: h, w, x, y = 0.0, 0.0, 0.0, 0.0 - assert 'polygon' in values - assert isinstance(values['polygon'], Polygon) - for polygon_path in values['polygon'].paths: + assert "polygon" in values + assert isinstance(values["polygon"], Polygon) + for polygon_path in values["polygon"].paths: for point in polygon_path: assert isinstance(point, Point) h = max(h, point.y) @@ -63,6 +75,7 @@ def validate_bounding_box(cls, v: Optional[BoundingBox], values: dict) -> Boundi v = BoundingBox(h=h, w=w, x=x, y=y) return v + class FrameAnnotation(AnnotationBase): frames: List interpolated: bool @@ -70,22 +83,23 @@ class FrameAnnotation(AnnotationBase): ranges: List[int] -AllowedAnnotation = Union[PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation] +AllowedAnnotation = Union[ + PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation +] + class Item(BaseModel): name: str path: str + class DarwinV2(BaseModel): version: Literal["2.0"] = "2.0" schema_ref: str item: dict annotations: List[AllowedAnnotation] - - @validator('schema_ref', always=True) + + @validator("schema_ref", always=True) def validate_schema_ref(cls, v: str) -> str: assert v.startswith("http") return v - - - diff --git a/darwin/future/data_objects/property.py b/darwin/future/data_objects/property.py deleted file mode 100644 index e69de29bb..000000000 From 088e37c9a5f028582fd96287475796ebf5b30caf Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 15:15:09 +0000 Subject: [PATCH 30/39] cleanup validator --- darwin/future/data_objects/darwin.py | 38 +++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index 0d0f5135a..c04143bbd 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -10,6 +10,12 @@ class Point(BaseModel): x: float y: float + + def __add__(self, other: Point) -> Point: + return Point(x=self.x + other.x, y=self.y + other.y) + + def __sub__(self, other: Point) -> Point: + return Point(x=self.x - other.x, y=self.y - other.y) PolygonPath = List[Point] @@ -17,6 +23,24 @@ class Point(BaseModel): class Polygon(BaseModel): paths: List[PolygonPath] + + def bounding_box(self) -> BoundingBox: + h, w, x, y = 0.0, 0.0, 0.0, 0.0 + for polygon_path in self.paths: + for point in polygon_path: + h = max(h, point.y) + w = max(w, point.x) + x = min(x, point.x) + y = min(y, point.y) + return BoundingBox(h=h, w=w, x=x, y=y) + + @property + def is_complex(self) -> bool: + return len(self.paths) > 1 + + @property + def center(self) -> Point: + return self.bounding_box().center class AnnotationBase(BaseModel): @@ -37,6 +61,10 @@ class BoundingBox(BaseModel): w: float x: float y: float + + @property + def center(self) -> Point: + return Point(x=self.x + self.w / 2, y=self.y + self.h / 2) class BoundingBoxAnnotation(AnnotationBase): @@ -62,17 +90,9 @@ def validate_bounding_box( cls, v: Optional[BoundingBox], values: dict ) -> BoundingBox: if v is None: - h, w, x, y = 0.0, 0.0, 0.0, 0.0 assert "polygon" in values assert isinstance(values["polygon"], Polygon) - for polygon_path in values["polygon"].paths: - for point in polygon_path: - assert isinstance(point, Point) - h = max(h, point.y) - w = max(w, point.x) - x = min(x, point.x) - y = min(y, point.y) - v = BoundingBox(h=h, w=w, x=x, y=y) + v = values["polygon"].bounding_box() return v From 653697aa9c09418c1a64146bfd3e49919eabf797 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 8 Dec 2023 15:31:16 +0000 Subject: [PATCH 31/39] test for properties base example --- darwin/future/data_objects/darwin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py index c04143bbd..2e4d334b7 100644 --- a/darwin/future/data_objects/darwin.py +++ b/darwin/future/data_objects/darwin.py @@ -10,10 +10,10 @@ class Point(BaseModel): x: float y: float - + def __add__(self, other: Point) -> Point: return Point(x=self.x + other.x, y=self.y + other.y) - + def __sub__(self, other: Point) -> Point: return Point(x=self.x - other.x, y=self.y - other.y) @@ -23,7 +23,7 @@ def __sub__(self, other: Point) -> Point: class Polygon(BaseModel): paths: List[PolygonPath] - + def bounding_box(self) -> BoundingBox: h, w, x, y = 0.0, 0.0, 0.0, 0.0 for polygon_path in self.paths: @@ -33,11 +33,11 @@ def bounding_box(self) -> BoundingBox: x = min(x, point.x) y = min(y, point.y) return BoundingBox(h=h, w=w, x=x, y=y) - + @property def is_complex(self) -> bool: return len(self.paths) > 1 - + @property def center(self) -> Point: return self.bounding_box().center @@ -61,7 +61,7 @@ class BoundingBox(BaseModel): w: float x: float y: float - + @property def center(self) -> Point: return Point(x=self.x + self.w / 2, y=self.y + self.h / 2) From f1670d127f27b7e1b881b6569a21abaedda0de7b Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 11 Dec 2023 22:16:31 +0000 Subject: [PATCH 32/39] Changes for list/get endpoint --- darwin/future/data_objects/darwin.py | 125 --------------------------- 1 file changed, 125 deletions(-) delete mode 100644 darwin/future/data_objects/darwin.py diff --git a/darwin/future/data_objects/darwin.py b/darwin/future/data_objects/darwin.py deleted file mode 100644 index 2e4d334b7..000000000 --- a/darwin/future/data_objects/darwin.py +++ /dev/null @@ -1,125 +0,0 @@ -from __future__ import annotations - -from typing import List, Literal, Optional, Union - -from pydantic import BaseModel, validator - -from darwin.future.data_objects.properties import SelectedProperty - - -class Point(BaseModel): - x: float - y: float - - def __add__(self, other: Point) -> Point: - return Point(x=self.x + other.x, y=self.y + other.y) - - def __sub__(self, other: Point) -> Point: - return Point(x=self.x - other.x, y=self.y - other.y) - - -PolygonPath = List[Point] - - -class Polygon(BaseModel): - paths: List[PolygonPath] - - def bounding_box(self) -> BoundingBox: - h, w, x, y = 0.0, 0.0, 0.0, 0.0 - for polygon_path in self.paths: - for point in polygon_path: - h = max(h, point.y) - w = max(w, point.x) - x = min(x, point.x) - y = min(y, point.y) - return BoundingBox(h=h, w=w, x=x, y=y) - - @property - def is_complex(self) -> bool: - return len(self.paths) > 1 - - @property - def center(self) -> Point: - return self.bounding_box().center - - -class AnnotationBase(BaseModel): - id: str - name: str - properties: Optional[SelectedProperty] = None - slot_names: Optional[List[str]] = None - - @validator("id", always=True) - def validate_id_is_UUID(cls, v: str) -> str: - assert len(v) == 36 - assert "-" in v - return v - - -class BoundingBox(BaseModel): - h: float - w: float - x: float - y: float - - @property - def center(self) -> Point: - return Point(x=self.x + self.w / 2, y=self.y + self.h / 2) - - -class BoundingBoxAnnotation(AnnotationBase): - bounding_box: BoundingBox - - -class Ellipse(BaseModel): - center: Point - radius: Point - angle: float - - -class EllipseAnnotation(AnnotationBase): - ellipse: Ellipse - - -class PolygonAnnotation(AnnotationBase): - polygon: Polygon - bounding_box: Optional[BoundingBox] = None - - @validator("bounding_box", pre=False, always=True) - def validate_bounding_box( - cls, v: Optional[BoundingBox], values: dict - ) -> BoundingBox: - if v is None: - assert "polygon" in values - assert isinstance(values["polygon"], Polygon) - v = values["polygon"].bounding_box() - return v - - -class FrameAnnotation(AnnotationBase): - frames: List - interpolated: bool - interpolate_algorithm: str - ranges: List[int] - - -AllowedAnnotation = Union[ - PolygonAnnotation, BoundingBoxAnnotation, EllipseAnnotation, FrameAnnotation -] - - -class Item(BaseModel): - name: str - path: str - - -class DarwinV2(BaseModel): - version: Literal["2.0"] = "2.0" - schema_ref: str - item: dict - annotations: List[AllowedAnnotation] - - @validator("schema_ref", always=True) - def validate_schema_ref(cls, v: str) -> str: - assert v.startswith("http") - return v From f55a12cbfddd8e6224c91cb212f1ef69e79cfba5 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 11 Dec 2023 23:20:30 +0000 Subject: [PATCH 33/39] get_all_properties --- darwin/future/core/properties/__init__.py | 0 darwin/future/core/properties/get.py | 30 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 darwin/future/core/properties/__init__.py create mode 100644 darwin/future/core/properties/get.py diff --git a/darwin/future/core/properties/__init__.py b/darwin/future/core/properties/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/darwin/future/core/properties/get.py b/darwin/future/core/properties/get.py new file mode 100644 index 000000000..15eab55a4 --- /dev/null +++ b/darwin/future/core/properties/get.py @@ -0,0 +1,30 @@ +from typing import List, Optional + +from pydantic import parse_obj_as + +from darwin.future.core.client import ClientCore +from darwin.future.data_objects.properties import FullProperty + + +def get_all_properties( + client: ClientCore, team_slug: Optional[str] = None +) -> List[FullProperty]: + """ + Returns a TeamCore 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: + TeamCore: The TeamCore object 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") + assert isinstance(response, dict) + return parse_obj_as(List[FullProperty], response.get("properties")) From c5fd34965b4912847342ffca217918572f372839 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 21:40:10 +0000 Subject: [PATCH 34/39] get properties and get by id --- darwin/future/core/properties/__init__.py | 5 ++ darwin/future/core/properties/get.py | 54 ++++++++++-- darwin/future/tests/core/fixtures.py | 26 ++++++ .../future/tests/core/properties/__init__.py | 0 .../future/tests/core/properties/test_get.py | 82 +++++++++++++++++++ 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 darwin/future/tests/core/properties/__init__.py create mode 100644 darwin/future/tests/core/properties/test_get.py diff --git a/darwin/future/core/properties/__init__.py b/darwin/future/core/properties/__init__.py index e69de29bb..ef53ea047 100644 --- a/darwin/future/core/properties/__init__.py +++ 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 index 15eab55a4..2f1637a6c 100644 --- a/darwin/future/core/properties/get.py +++ b/darwin/future/core/properties/get.py @@ -1,16 +1,20 @@ -from typing import List, Optional +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_all_properties( - client: ClientCore, team_slug: Optional[str] = None +def get_team_properties( + client: ClientCore, + team_slug: Optional[str] = None, + params: Optional[QueryString] = None, ) -> List[FullProperty]: """ - Returns a TeamCore object for the specified team slug. + Returns a List[FullProperty] object for the specified team slug. Parameters: client (ClientCore): The client to use for the request. @@ -18,13 +22,51 @@ def get_all_properties( default team from the client's config will be used. Returns: - TeamCore: The TeamCore object for the specified team slug. + 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") + 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 + 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/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 83afe5e54..4623e7c29 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -8,11 +8,37 @@ 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..5c888051e --- /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 \ No newline at end of file From 91ac90b4f53f800b83cef2f3927b15959b412d62 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 21:54:57 +0000 Subject: [PATCH 35/39] linting --- darwin/future/tests/core/fixtures.py | 2 ++ darwin/future/tests/core/properties/test_get.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/darwin/future/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index 4623e7c29..8c7434416 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -24,6 +24,7 @@ def base_property_option() -> PropertyOption: color="rgba(0,0,0,0)", ) + @pytest.fixture def base_property_object(base_property_option: PropertyOption) -> FullProperty: return FullProperty( @@ -39,6 +40,7 @@ def base_property_object(base_property_option: PropertyOption) -> FullProperty: options=[base_property_option], ) + @pytest.fixture def base_config() -> DarwinConfig: return DarwinConfig( diff --git a/darwin/future/tests/core/properties/test_get.py b/darwin/future/tests/core/properties/test_get.py index 5c888051e..98c0d5609 100644 --- a/darwin/future/tests/core/properties/test_get.py +++ b/darwin/future/tests/core/properties/test_get.py @@ -24,7 +24,7 @@ def test_get_team_properties( responses.GET, f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties", json=response_data, - status=200 + status=200, ) # Call the function being tested @@ -49,7 +49,7 @@ def test_get_team_full_properties( query_param_matcher({"include_values": "True"}), ], json=response_data, - status=200 + status=200, ) params = QueryString({"include_values": True}) # Call the function being tested @@ -79,4 +79,4 @@ def test_get_property_by_id( # Assertions assert isinstance(property, FullProperty) - assert property == base_property_object \ No newline at end of file + assert property == base_property_object From 92cd2b3b5bce6b47f2ef0871de8c5186c7ad64bf Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 22:10:00 +0000 Subject: [PATCH 36/39] linting --- darwin/future/tests/data_objects/test_darwin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/darwin/future/tests/data_objects/test_darwin.py b/darwin/future/tests/data_objects/test_darwin.py index be75131ef..36fd30767 100644 --- a/darwin/future/tests/data_objects/test_darwin.py +++ b/darwin/future/tests/data_objects/test_darwin.py @@ -39,6 +39,7 @@ def test_polygon_annotation(raw_json: dict): polygon_annotation = raw_json["annotations"][2] PolygonAnnotation.parse_obj(polygon_annotation) + def test_polygon_bbx_validator(raw_json: dict): polygon_annotation = raw_json["annotations"][2] without_bbx = polygon_annotation.copy() @@ -49,5 +50,5 @@ def test_polygon_bbx_validator(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] + bounds_annotation = raw_json["annotations"][0] BoundingBoxAnnotation.parse_obj(bounds_annotation) From 69d8ebff092e9a175924f9be4cabea743aa5ce09 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 12 Dec 2023 22:24:50 +0000 Subject: [PATCH 37/39] QS helper and params checker on team_full_properties --- darwin/future/core/properties/get.py | 2 +- darwin/future/core/types/common.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/darwin/future/core/properties/get.py b/darwin/future/core/properties/get.py index 2f1637a6c..23a80a380 100644 --- a/darwin/future/core/properties/get.py +++ b/darwin/future/core/properties/get.py @@ -41,7 +41,7 @@ def get_team_full_properties( ) -> List[FullProperty]: params = ( params + QueryString({"include_values": True}) - if params + if params and not params.get("include_values") else QueryString({"include_values": True}) ) return get_team_properties(client, team_slug, params) diff --git a/darwin/future/core/types/common.py b/darwin/future/core/types/common.py index 9725c2d64..dfcd90fb7 100644 --- a/darwin/future/core/types/common.py +++ b/darwin/future/core/types/common.py @@ -108,3 +108,6 @@ def __str__(self) -> str: 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) From 4fcfbdd2a312f2254ce3542692ee54c6ec6bbeac Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Wed, 13 Dec 2023 10:12:51 +0000 Subject: [PATCH 38/39] change for lower case True --- darwin/future/core/types/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darwin/future/core/types/common.py b/darwin/future/core/types/common.py index dfcd90fb7..a282e51f5 100644 --- a/darwin/future/core/types/common.py +++ b/darwin/future/core/types/common.py @@ -101,9 +101,9 @@ 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: From e2a2e422ac23385c192308fa6d4a4b5868ed81f0 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Wed, 13 Dec 2023 10:23:32 +0000 Subject: [PATCH 39/39] test change --- darwin/future/tests/core/properties/test_get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darwin/future/tests/core/properties/test_get.py b/darwin/future/tests/core/properties/test_get.py index 98c0d5609..3b2b5c2ba 100644 --- a/darwin/future/tests/core/properties/test_get.py +++ b/darwin/future/tests/core/properties/test_get.py @@ -46,7 +46,7 @@ def test_get_team_full_properties( responses.GET, f"{base_client.config.base_url}api/v2/teams/{base_client.config.default_team}/properties", match=[ - query_param_matcher({"include_values": "True"}), + query_param_matcher({"include_values": "true"}), ], json=response_data, status=200,