Skip to content

Commit

Permalink
[PY-407][PY-534][PY-404] Item + ItemQuery objects (#715)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Nathanjp91 authored Nov 13, 2023
1 parent f1b8f3c commit 0efb835
Show file tree
Hide file tree
Showing 25 changed files with 482 additions and 54 deletions.
26 changes: 17 additions & 9 deletions darwin/future/core/items/get.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import List, Tuple, Union
from __future__ import annotations

from typing import List, Literal, Tuple, Union
from uuid import UUID

from pydantic import ValidationError, parse_obj_as

from darwin.future.core.client import ClientCore
from darwin.future.core.types.common import QueryString
from darwin.future.data_objects.item import Folder, Item
from darwin.future.data_objects.item import Folder, ItemCore


def get_item_ids(
Expand Down Expand Up @@ -86,7 +88,7 @@ def get_item(
team_slug: str,
item_id: Union[UUID, str],
params: QueryString = QueryString({}),
) -> Item:
) -> ItemCore:
"""
Returns an item
Expand All @@ -106,14 +108,15 @@ def get_item(
"""
response = api_client.get(f"/v2/teams/{team_slug}/items/{item_id}", params)
assert isinstance(response, dict)
return parse_obj_as(Item, response)
return parse_obj_as(ItemCore, response)


def list_items(
api_client: ClientCore,
team_slug: str,
params: QueryString,
) -> Tuple[List[Item], List[ValidationError]]:
dataset_ids: int | list[int] | Literal["all"],
params: QueryString = QueryString({}),
) -> Tuple[List[ItemCore], List[ValidationError]]:
"""
Returns a list of items for the dataset
Expand All @@ -133,15 +136,20 @@ def list_items(
List[ValidationError]
A list of ValidationError on failed objects
"""
assert "dataset_ids" in params.value, "dataset_ids must be provided"
dataset_ids = (
dataset_ids
if isinstance(dataset_ids, list) or dataset_ids == "all"
else [dataset_ids]
)
params = params + QueryString({"dataset_ids": dataset_ids})
response = api_client.get(f"/v2/teams/{team_slug}/items", params)
assert isinstance(response, dict)
items: List[Item] = []
items: List[ItemCore] = []
exceptions: List[ValidationError] = []
for item in response["items"]:
assert isinstance(item, dict)
try:
items.append(parse_obj_as(Item, item))
items.append(parse_obj_as(ItemCore, item))
except ValidationError as e:
exceptions.append(e)
return items, exceptions
Expand Down
38 changes: 29 additions & 9 deletions darwin/future/core/types/common.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from __future__ import annotations

from typing import Any, Dict, List, Union
from typing import Any, Dict, List, Mapping, Protocol, Union

from darwin.future.data_objects import validators as darwin_validators
from darwin.future.data_objects.typing import UnknownType

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


class Implements_str(Protocol):
def __str__(self) -> str:
...


Stringable = Union[str, Implements_str]


class TeamSlug(str):
"""
Represents a team slug, which is a string identifier for a team.
Expand Down Expand Up @@ -73,18 +80,31 @@ class QueryString:
Returns a string representation of the QueryString object, in the format "?key1=value1&key2=value2".
"""

value: Dict[str, str]
value: dict[str, list[str] | str]

def dict_check(self, value: UnknownType) -> Dict[str, str]:
assert isinstance(value, dict)
assert all(isinstance(k, str) and isinstance(v, str) for k, v in value.items())
return value
def dict_check(
self, value: Mapping[str, list[Stringable] | Stringable]
) -> dict[str, list[str] | str]:
mapped: dict[str, list[str] | str] = {}
for k, v in value.items():
if isinstance(v, list):
mapped[k] = [str(x) for x in v]
else:
mapped[k] = str(v)
return mapped

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

def __str__(self) -> str:
return "?" + "&".join(f"{k}={v}" for k, v in self.value.items())
output: str = "?" if self.value else ""
for k, v in self.value.items():
if isinstance(v, list):
for x in v:
output += f"{k}={x}&"
else:
output += f"{k}={v}&"
return output[:-1] # remove trailing &

def __add__(self, other: QueryString) -> QueryString:
return QueryString({**self.value, **other.value})
5 changes: 2 additions & 3 deletions darwin/future/data_objects/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def validate_no_slashes(v: UnknownType) -> str:
assert isinstance(v, str), "Must be a string"
assert len(v) > 0, "cannot be empty"
assert "/" not in v, "cannot contain slashes"
assert " " not in v, "cannot contain spaces"

return v

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

Expand Down Expand Up @@ -127,7 +126,7 @@ def validate_name(cls, v: UnknownType) -> str:
return validate_no_slashes(v)


class Item(DefaultDarwin):
class ItemCore(DefaultDarwin):
# GraphotateWeb.Schemas.DatasetsV2.ItemRegistration.NewItem

# Required fields
Expand Down
7 changes: 7 additions & 0 deletions darwin/future/meta/objects/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import List, Optional, Sequence, Union


from darwin.cli_functions import upload_data
from darwin.dataset.upload_manager import LocalFile
from darwin.datatypes import PathLike
Expand All @@ -10,6 +11,7 @@
from darwin.future.data_objects.dataset import DatasetCore
from darwin.future.helpers.assertion import assert_is
from darwin.future.meta.objects.base import MetaBase
from darwin.future.meta.queries.item import ItemQuery
from darwin.future.meta.queries.item_id import ItemIDQuery


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

@property
def items(self) -> ItemQuery:
meta_params = {"dataset_ids": self.id, **self.meta_params}
return ItemQuery(self.client, meta_params=meta_params)

@classmethod
def create_dataset(cls, client: ClientCore, slug: str) -> DatasetCore:
"""
Expand Down
94 changes: 94 additions & 0 deletions darwin/future/meta/objects/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from __future__ import annotations

from typing import Dict, List, Optional, Union, cast
from uuid import UUID

from darwin.future.core.items.delete_items import delete_list_of_items
from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot
from darwin.future.meta.objects.base import MetaBase


class Item(MetaBase[ItemCore]):
"""
Represents an item in a Darwin dataset.
Args:
MetaBase (Stage): Generic MetaBase object expanded by ItemCore object
return type
Attributes:
name (str): The name of the item.
id (UUID): The unique identifier of the item.
slots (List[ItemSlot]): A list of slots associated with the item.
path (str): The path of the item.
dataset_id (int): The ID of the dataset the item belongs to.
processing_status (str): The processing status of the item.
archived (Optional[bool]): Whether the item is archived or not.
priority (Optional[int]): The priority of the item.
tags (Optional[Union[List[str], Dict[str, str]]]): The tags associated with the item.
layout (Optional[ItemLayout]): The layout of the item.
Methods:
delete(self) -> None:
Deletes the item from the Darwin dataset.
Example usage:
# Get the item object
items = workflow.items.where(name='test').collect() # gets first page of items
# Delete the items
[item.delete() for item in items] # will collect all pages of items and delete individually
"""

def delete(self) -> None:
team_slug, dataset_id = (
self.meta_params["team_slug"],
self.meta_params["dataset_id"]
if "dataset_id" in self.meta_params
else self.meta_params["dataset_ids"],
)
assert isinstance(team_slug, str)
dataset_id = cast(Union[int, List[int]], dataset_id)
filters = {"item_ids": [str(self.id)]}
delete_list_of_items(self.client, team_slug, dataset_id, filters)

@property
def name(self) -> str:
return self._element.name

@property
def id(self) -> UUID:
return self._element.id

@property
def slots(self) -> List[ItemSlot]:
return self._element.slots

@property
def path(self) -> str:
return self._element.path

@property
def dataset_id(self) -> int:
return self._element.dataset_id

@property
def processing_status(self) -> str:
return self._element.processing_status

@property
def archived(self) -> Optional[bool]:
return self._element.archived

@property
def priority(self) -> Optional[int]:
return self._element.priority

@property
def tags(self) -> Optional[Union[List[str], Dict[str, str]]]:
return self._element.tags

@property
def layout(self) -> Optional[ItemLayout]:
return self._element.layout
17 changes: 17 additions & 0 deletions darwin/future/meta/objects/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from darwin.future.core.types.query import QueryFilter
from darwin.future.data_objects.workflow import WFEdgeCore, WFStageCore
from darwin.future.meta.objects.base import MetaBase
from darwin.future.meta.queries.item import ItemQuery
from darwin.future.meta.queries.item_id import ItemIDQuery


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

@property
def items(self) -> ItemQuery:
"""Item ids attached to the stage
Returns:
List[Item]: List of item ids
"""
assert self._element.id is not None
return ItemQuery(
self.client,
meta_params=self.meta_params,
filters=[
QueryFilter(name="workflow_stage_ids", param=str(self._element.id))
],
)

@property
def item_ids(self) -> ItemIDQuery:
"""Item ids attached to the stage
Expand Down
7 changes: 7 additions & 0 deletions darwin/future/meta/objects/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from darwin.future.meta.objects.base import MetaBase
from darwin.future.meta.objects.dataset import Dataset
from darwin.future.meta.queries.dataset import DatasetQuery
from darwin.future.meta.queries.item import ItemQuery
from darwin.future.meta.queries.team_member import TeamMemberQuery
from darwin.future.meta.queries.workflow import WorkflowQuery

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

@property
def items(self) -> ItemQuery:
return ItemQuery(
self.client, meta_params={"team_slug": self.slug, "dataset_ids": "all"}
)

@classmethod
def delete_dataset(cls, client: ClientCore, dataset_id: Union[int, str]) -> int:
"""
Expand Down
10 changes: 10 additions & 0 deletions darwin/future/meta/objects/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
from darwin.cli_functions import upload_data
from darwin.dataset.upload_manager import LocalFile
from darwin.datatypes import PathLike
from darwin.future.core.types.query import QueryFilter
from darwin.future.data_objects.workflow import WFDatasetCore, WFTypeCore, WorkflowCore
from darwin.future.meta.objects.base import MetaBase
from darwin.future.meta.queries.item import ItemQuery
from darwin.future.meta.queries.stage import StageQuery


Expand Down Expand Up @@ -46,6 +48,14 @@ class Workflow(MetaBase[WorkflowCore]):
datasets = workflow.datasets
"""

@property
def items(self) -> ItemQuery:
return ItemQuery(
self.client,
meta_params=self.meta_params,
filters=[QueryFilter(name="workflow_id", param=str(self.id))],
)

@property
def stages(self) -> StageQuery:
meta_params = self.meta_params.copy()
Expand Down
Loading

0 comments on commit 0efb835

Please sign in to comment.