Skip to content

Commit 0efb835

Browse files
authored
[PY-407][PY-534][PY-404] Item + ItemQuery objects (#715)
* ItemCore changes for Query * ItemQuery * Changes for Item Query * filters addition * filters addition * teams + dataset: ItemQuery * linting fixes * changes to core api delete * delete typing * fixes to delete and item instantiation * linting * stage items * items query test basics * linting * tests for item + query * linting * cleanup * workflow items * item documentation * comments on delete * non-working validator removed
1 parent f1b8f3c commit 0efb835

25 files changed

+482
-54
lines changed

darwin/future/core/items/get.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
from typing import List, Tuple, Union
1+
from __future__ import annotations
2+
3+
from typing import List, Literal, Tuple, Union
24
from uuid import UUID
35

46
from pydantic import ValidationError, parse_obj_as
57

68
from darwin.future.core.client import ClientCore
79
from darwin.future.core.types.common import QueryString
8-
from darwin.future.data_objects.item import Folder, Item
10+
from darwin.future.data_objects.item import Folder, ItemCore
911

1012

1113
def get_item_ids(
@@ -86,7 +88,7 @@ def get_item(
8688
team_slug: str,
8789
item_id: Union[UUID, str],
8890
params: QueryString = QueryString({}),
89-
) -> Item:
91+
) -> ItemCore:
9092
"""
9193
Returns an item
9294
@@ -106,14 +108,15 @@ def get_item(
106108
"""
107109
response = api_client.get(f"/v2/teams/{team_slug}/items/{item_id}", params)
108110
assert isinstance(response, dict)
109-
return parse_obj_as(Item, response)
111+
return parse_obj_as(ItemCore, response)
110112

111113

112114
def list_items(
113115
api_client: ClientCore,
114116
team_slug: str,
115-
params: QueryString,
116-
) -> Tuple[List[Item], List[ValidationError]]:
117+
dataset_ids: int | list[int] | Literal["all"],
118+
params: QueryString = QueryString({}),
119+
) -> Tuple[List[ItemCore], List[ValidationError]]:
117120
"""
118121
Returns a list of items for the dataset
119122
@@ -133,15 +136,20 @@ def list_items(
133136
List[ValidationError]
134137
A list of ValidationError on failed objects
135138
"""
136-
assert "dataset_ids" in params.value, "dataset_ids must be provided"
139+
dataset_ids = (
140+
dataset_ids
141+
if isinstance(dataset_ids, list) or dataset_ids == "all"
142+
else [dataset_ids]
143+
)
144+
params = params + QueryString({"dataset_ids": dataset_ids})
137145
response = api_client.get(f"/v2/teams/{team_slug}/items", params)
138146
assert isinstance(response, dict)
139-
items: List[Item] = []
147+
items: List[ItemCore] = []
140148
exceptions: List[ValidationError] = []
141149
for item in response["items"]:
142150
assert isinstance(item, dict)
143151
try:
144-
items.append(parse_obj_as(Item, item))
152+
items.append(parse_obj_as(ItemCore, item))
145153
except ValidationError as e:
146154
exceptions.append(e)
147155
return items, exceptions

darwin/future/core/types/common.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
from __future__ import annotations
22

3-
from typing import Any, Dict, List, Union
3+
from typing import Any, Dict, List, Mapping, Protocol, Union
44

55
from darwin.future.data_objects import validators as darwin_validators
6-
from darwin.future.data_objects.typing import UnknownType
76

87
JSONType = Union[Dict[str, Any], List[Dict[str, Any]]] # type: ignore
98

109

10+
class Implements_str(Protocol):
11+
def __str__(self) -> str:
12+
...
13+
14+
15+
Stringable = Union[str, Implements_str]
16+
17+
1118
class TeamSlug(str):
1219
"""
1320
Represents a team slug, which is a string identifier for a team.
@@ -73,18 +80,31 @@ class QueryString:
7380
Returns a string representation of the QueryString object, in the format "?key1=value1&key2=value2".
7481
"""
7582

76-
value: Dict[str, str]
83+
value: dict[str, list[str] | str]
7784

78-
def dict_check(self, value: UnknownType) -> Dict[str, str]:
79-
assert isinstance(value, dict)
80-
assert all(isinstance(k, str) and isinstance(v, str) for k, v in value.items())
81-
return value
85+
def dict_check(
86+
self, value: Mapping[str, list[Stringable] | Stringable]
87+
) -> dict[str, list[str] | str]:
88+
mapped: dict[str, list[str] | str] = {}
89+
for k, v in value.items():
90+
if isinstance(v, list):
91+
mapped[k] = [str(x) for x in v]
92+
else:
93+
mapped[k] = str(v)
94+
return mapped
8295

83-
def __init__(self, value: Dict[str, str]) -> None:
96+
def __init__(self, value: Mapping[str, list[Stringable] | Stringable]) -> None:
8497
self.value = self.dict_check(value)
8598

8699
def __str__(self) -> str:
87-
return "?" + "&".join(f"{k}={v}" for k, v in self.value.items())
100+
output: str = "?" if self.value else ""
101+
for k, v in self.value.items():
102+
if isinstance(v, list):
103+
for x in v:
104+
output += f"{k}={x}&"
105+
else:
106+
output += f"{k}={v}&"
107+
return output[:-1] # remove trailing &
88108

89109
def __add__(self, other: QueryString) -> QueryString:
90110
return QueryString({**self.value, **other.value})

darwin/future/data_objects/item.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ def validate_no_slashes(v: UnknownType) -> str:
1515
assert isinstance(v, str), "Must be a string"
1616
assert len(v) > 0, "cannot be empty"
1717
assert "/" not in v, "cannot contain slashes"
18-
assert " " not in v, "cannot contain spaces"
1918

2019
return v
2120

@@ -75,7 +74,7 @@ def validate_fps(cls, values: dict) -> dict:
7574
elif isinstance(value, (int, float)):
7675
type = values.get("type")
7776
if type == "image":
78-
assert value == 0, "fps must be 0 for images"
77+
assert value == 0 or value == 1.0, "fps must be '0' or '1.0' for images"
7978
else:
8079
assert value >= 0, "fps must be greater than or equal to 0 for videos"
8180

@@ -127,7 +126,7 @@ def validate_name(cls, v: UnknownType) -> str:
127126
return validate_no_slashes(v)
128127

129128

130-
class Item(DefaultDarwin):
129+
class ItemCore(DefaultDarwin):
131130
# GraphotateWeb.Schemas.DatasetsV2.ItemRegistration.NewItem
132131

133132
# Required fields

darwin/future/meta/objects/dataset.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import List, Optional, Sequence, Union
44

5+
56
from darwin.cli_functions import upload_data
67
from darwin.dataset.upload_manager import LocalFile
78
from darwin.datatypes import PathLike
@@ -10,6 +11,7 @@
1011
from darwin.future.data_objects.dataset import DatasetCore
1112
from darwin.future.helpers.assertion import assert_is
1213
from darwin.future.meta.objects.base import MetaBase
14+
from darwin.future.meta.queries.item import ItemQuery
1315
from darwin.future.meta.queries.item_id import ItemIDQuery
1416

1517

@@ -74,6 +76,11 @@ def item_ids(self) -> ItemIDQuery:
7476
meta_params = {"dataset_ids": self.id, **self.meta_params}
7577
return ItemIDQuery(self.client, meta_params=meta_params)
7678

79+
@property
80+
def items(self) -> ItemQuery:
81+
meta_params = {"dataset_ids": self.id, **self.meta_params}
82+
return ItemQuery(self.client, meta_params=meta_params)
83+
7784
@classmethod
7885
def create_dataset(cls, client: ClientCore, slug: str) -> DatasetCore:
7986
"""

darwin/future/meta/objects/item.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from __future__ import annotations
2+
3+
from typing import Dict, List, Optional, Union, cast
4+
from uuid import UUID
5+
6+
from darwin.future.core.items.delete_items import delete_list_of_items
7+
from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot
8+
from darwin.future.meta.objects.base import MetaBase
9+
10+
11+
class Item(MetaBase[ItemCore]):
12+
"""
13+
Represents an item in a Darwin dataset.
14+
15+
Args:
16+
MetaBase (Stage): Generic MetaBase object expanded by ItemCore object
17+
return type
18+
19+
Attributes:
20+
name (str): The name of the item.
21+
id (UUID): The unique identifier of the item.
22+
slots (List[ItemSlot]): A list of slots associated with the item.
23+
path (str): The path of the item.
24+
dataset_id (int): The ID of the dataset the item belongs to.
25+
processing_status (str): The processing status of the item.
26+
archived (Optional[bool]): Whether the item is archived or not.
27+
priority (Optional[int]): The priority of the item.
28+
tags (Optional[Union[List[str], Dict[str, str]]]): The tags associated with the item.
29+
layout (Optional[ItemLayout]): The layout of the item.
30+
31+
Methods:
32+
delete(self) -> None:
33+
Deletes the item from the Darwin dataset.
34+
35+
Example usage:
36+
# Get the item object
37+
items = workflow.items.where(name='test').collect() # gets first page of items
38+
39+
# Delete the items
40+
[item.delete() for item in items] # will collect all pages of items and delete individually
41+
42+
"""
43+
44+
def delete(self) -> None:
45+
team_slug, dataset_id = (
46+
self.meta_params["team_slug"],
47+
self.meta_params["dataset_id"]
48+
if "dataset_id" in self.meta_params
49+
else self.meta_params["dataset_ids"],
50+
)
51+
assert isinstance(team_slug, str)
52+
dataset_id = cast(Union[int, List[int]], dataset_id)
53+
filters = {"item_ids": [str(self.id)]}
54+
delete_list_of_items(self.client, team_slug, dataset_id, filters)
55+
56+
@property
57+
def name(self) -> str:
58+
return self._element.name
59+
60+
@property
61+
def id(self) -> UUID:
62+
return self._element.id
63+
64+
@property
65+
def slots(self) -> List[ItemSlot]:
66+
return self._element.slots
67+
68+
@property
69+
def path(self) -> str:
70+
return self._element.path
71+
72+
@property
73+
def dataset_id(self) -> int:
74+
return self._element.dataset_id
75+
76+
@property
77+
def processing_status(self) -> str:
78+
return self._element.processing_status
79+
80+
@property
81+
def archived(self) -> Optional[bool]:
82+
return self._element.archived
83+
84+
@property
85+
def priority(self) -> Optional[int]:
86+
return self._element.priority
87+
88+
@property
89+
def tags(self) -> Optional[Union[List[str], Dict[str, str]]]:
90+
return self._element.tags
91+
92+
@property
93+
def layout(self) -> Optional[ItemLayout]:
94+
return self._element.layout

darwin/future/meta/objects/stage.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from darwin.future.core.types.query import QueryFilter
88
from darwin.future.data_objects.workflow import WFEdgeCore, WFStageCore
99
from darwin.future.meta.objects.base import MetaBase
10+
from darwin.future.meta.queries.item import ItemQuery
1011
from darwin.future.meta.queries.item_id import ItemIDQuery
1112

1213

@@ -43,6 +44,22 @@ class Stage(MetaBase[WFStageCore]):
4344
stage.move_attached_files_to_stage(new_stage_id=new_stage.id)
4445
"""
4546

47+
@property
48+
def items(self) -> ItemQuery:
49+
"""Item ids attached to the stage
50+
51+
Returns:
52+
List[Item]: List of item ids
53+
"""
54+
assert self._element.id is not None
55+
return ItemQuery(
56+
self.client,
57+
meta_params=self.meta_params,
58+
filters=[
59+
QueryFilter(name="workflow_stage_ids", param=str(self._element.id))
60+
],
61+
)
62+
4663
@property
4764
def item_ids(self) -> ItemIDQuery:
4865
"""Item ids attached to the stage

darwin/future/meta/objects/team.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from darwin.future.meta.objects.base import MetaBase
1010
from darwin.future.meta.objects.dataset import Dataset
1111
from darwin.future.meta.queries.dataset import DatasetQuery
12+
from darwin.future.meta.queries.item import ItemQuery
1213
from darwin.future.meta.queries.team_member import TeamMemberQuery
1314
from darwin.future.meta.queries.workflow import WorkflowQuery
1415

@@ -95,6 +96,12 @@ def datasets(self) -> DatasetQuery:
9596
def workflows(self) -> WorkflowQuery:
9697
return WorkflowQuery(self.client, meta_params={"team_slug": self.slug})
9798

99+
@property
100+
def items(self) -> ItemQuery:
101+
return ItemQuery(
102+
self.client, meta_params={"team_slug": self.slug, "dataset_ids": "all"}
103+
)
104+
98105
@classmethod
99106
def delete_dataset(cls, client: ClientCore, dataset_id: Union[int, str]) -> int:
100107
"""

darwin/future/meta/objects/workflow.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from darwin.cli_functions import upload_data
77
from darwin.dataset.upload_manager import LocalFile
88
from darwin.datatypes import PathLike
9+
from darwin.future.core.types.query import QueryFilter
910
from darwin.future.data_objects.workflow import WFDatasetCore, WFTypeCore, WorkflowCore
1011
from darwin.future.meta.objects.base import MetaBase
12+
from darwin.future.meta.queries.item import ItemQuery
1113
from darwin.future.meta.queries.stage import StageQuery
1214

1315

@@ -46,6 +48,14 @@ class Workflow(MetaBase[WorkflowCore]):
4648
datasets = workflow.datasets
4749
"""
4850

51+
@property
52+
def items(self) -> ItemQuery:
53+
return ItemQuery(
54+
self.client,
55+
meta_params=self.meta_params,
56+
filters=[QueryFilter(name="workflow_id", param=str(self.id))],
57+
)
58+
4959
@property
5060
def stages(self) -> StageQuery:
5161
meta_params = self.meta_params.copy()

0 commit comments

Comments
 (0)