diff --git a/.vscode/settings.json b/.vscode/settings.json index 172ef499b..e8a1f4071 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,6 @@ "black" ], "python.testing.pytestEnabled": true, - "python.linting.enabled": true + "python.linting.enabled": true, + "python.analysis.typeCheckingMode": "basic" } \ No newline at end of file diff --git a/darwin/future/core/types/query.py b/darwin/future/core/types/query.py index 392628e52..c6460ef98 100644 --- a/darwin/future/core/types/query.py +++ b/darwin/future/core/types/query.py @@ -2,12 +2,14 @@ from abc import ABC, abstractmethod from enum import Enum -from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar +from typing import Any, Callable, Dict, Generic, List, Mapping, Optional, TypeVar from darwin.future.core.client import Client +from darwin.future.meta.objects.base import MetaBase from darwin.future.pydantic_base import DefaultDarwin -T = TypeVar("T", bound=DefaultDarwin) +T = TypeVar("T", bound=MetaBase) +R = TypeVar("R", bound=DefaultDarwin) Param = Dict[str, Any] # type: ignore @@ -55,7 +57,7 @@ def filter_attr(self, attr: Any) -> bool: # type: ignore raise ValueError(f"Unknown modifier {self.modifier}") -class Query(Generic[T], ABC): +class Query(Generic[T, R], ABC): """Basic Query object with methods to manage filters Methods: filter: adds a filter to the query object, returns a new query object @@ -64,27 +66,29 @@ class Query(Generic[T], ABC): _generic_execute_filter: Executes a filter on a list of objects """ - def __init__(self, filters: Optional[List[QueryFilter]] = None): + def __init__(self, client: Client, filters: Optional[List[QueryFilter]] = None): + self.client = client self.filters = filters + self.results: Optional[List[R]] = None - def filter(self, filter: QueryFilter) -> Query[T]: + def filter(self, filter: QueryFilter) -> Query[T, R]: return self + filter - def __add__(self, filter: QueryFilter) -> Query[T]: + def __add__(self, filter: QueryFilter) -> Query[T, R]: assert filter is not None assert isinstance(filter, QueryFilter) if self.filters is None: self.filters = [] - return self.__class__([*self.filters, filter]) + return self.__class__(self.client, [*self.filters, filter]) - def __sub__(self, filter: QueryFilter) -> Query[T]: + def __sub__(self, filter: QueryFilter) -> Query[T, R]: assert filter is not None assert isinstance(filter, QueryFilter) if self.filters is None: return self - return self.__class__([f for f in self.filters if f != filter]) + return self.__class__(self.client, [f for f in self.filters if f != filter]) - def __iadd__(self, filter: QueryFilter) -> Query[T]: + def __iadd__(self, filter: QueryFilter) -> Query[T, R]: assert filter is not None assert isinstance(filter, QueryFilter) if self.filters is None: @@ -93,7 +97,7 @@ def __iadd__(self, filter: QueryFilter) -> Query[T]: self.filters.append(filter) return self - def __isub__(self, filter: QueryFilter) -> Query[T]: + def __isub__(self, filter: QueryFilter) -> Query[T, R]: assert filter is not None assert isinstance(filter, QueryFilter) if self.filters is None: @@ -106,47 +110,38 @@ def __len__(self) -> int: return 0 return len(self.filters) - def __iter__(self) -> Query[T]: + def __iter__(self) -> Query[T, R]: self.n = 0 return self - def __next__(self) -> QueryFilter: - if self.filters is None: - self.filters = [] - if self.n < len(self.filters): - result = self.filters[self.n] + def __next__(self) -> R: + if self.results is None: + self.results = list(self.collect()) + if self.n < len(self.results): + result = self.results[self.n] self.n += 1 return result else: raise StopIteration + + def __getitem__(self, index: int) -> R: + if self.results is None: + self.results = list(self.collect()) + return self.results[index] + + def __setitem__(self, index: int, value: R) -> None: + if self.results is None: + self.results = list(self.collect()) + self.results[index] = value @abstractmethod - def where(self, param: Param) -> Query[T]: + def where(self, param: Param) -> Query[T, R]: raise NotImplementedError("Not implemented") @abstractmethod - def collect(self, client: Client) -> List[T]: + def collect(self) -> T: raise NotImplementedError("Not implemented") - def _generic_execute_filter(self, objects: List[T], filter: QueryFilter) -> List[T]: + def _generic_execute_filter(self, objects: List[R], filter: QueryFilter) -> List[R]: return [m for m in objects if filter.filter_attr(getattr(m, filter.name))] - -class ServerSideQuery(Query): - """Server side query object - TODO: add server specific methods and paramenters - """ - - def __init__(self, filters: Optional[List[QueryFilter]] = None, client: Optional[Client] = None): - self.client = client - super().__init__(filters=filters) - - -class ClientSideQuery(Query): - """Client side query object - TODO: add client side specific methods and parameters - """ - - def __init__(self, filters: Optional[List[QueryFilter]] = None, objects: Optional[List[T]] = None): - self.objects = objects - super().__init__(filters=filters) diff --git a/darwin/future/data_objects/team.py b/darwin/future/data_objects/team.py index 3e3cd403d..5512c5044 100644 --- a/darwin/future/data_objects/team.py +++ b/darwin/future/data_objects/team.py @@ -1,7 +1,10 @@ -from typing import List, Optional +from __future__ import annotations + +from typing import List, Optional, Tuple from pydantic import validator +from darwin.future.core.client import Client from darwin.future.data_objects.dataset import DatasetList from darwin.future.data_objects.team_member_role import TeamMemberRole from darwin.future.data_objects.validators import parse_name @@ -51,5 +54,56 @@ class Team(DefaultDarwin): # Data Validation _slug_validator = validator("slug", allow_reuse=True)(parse_name) + @staticmethod + def from_client(client: Client, team_slug: Optional[str] = None) -> Team: + """Returns the team with the given slug from the client + + Args: + client (Client): Core client object + team_slug (Optional[str], optional): team slug str, Defaults to None. + + Returns: + Team: Team object retrieved from the client with the given slug + """ + if not team_slug: + team_slug = client.config.default_team + return get_team(client, team_slug) + TeamList = List[Team] + + +def get_team(client: Client, team_slug: Optional[str] = None) -> Team: + """Returns the team with the given slug from the client + + Args: + client (Client): Core client object + team_slug (Optional[str], optional): team slug str, Defaults to None. + + Returns: + Team: Team object retrieved from the client with the given slug + """ + if not team_slug: + team_slug = client.config.default_team + response = client.get(f"/teams/{team_slug}/") + return Team.parse_obj(response) + + +def get_team_members(client: Client) -> Tuple[List[TeamMember], List[Exception]]: + """Returns a list of team members for the given client + + Args: + client (Client): Core client object + + Returns: + Tuple[List[TeamMember], List[Exception]]: List of team members and list of errors if any + """ + response = client.get(f"/memberships") + members = [] + errors = [] + for item in response: + try: + members.append(TeamMember.parse_obj(item)) + except Exception as e: + errors.append(e) + return members, errors diff --git a/darwin/future/meta/client.py b/darwin/future/meta/client.py index a5f896138..b8fc8646c 100644 --- a/darwin/future/meta/client.py +++ b/darwin/future/meta/client.py @@ -6,10 +6,12 @@ from requests.adapters import Retry from darwin.future.core.client import Client, DarwinConfig +from darwin.future.meta.objects.team import TeamMeta class MetaClient(Client): def __init__(self, config: DarwinConfig, retries: Optional[Retry] = None) -> None: + self._team: Optional[TeamMeta] = None super().__init__(config, retries=retries) @classmethod @@ -27,3 +29,9 @@ def from_api_key(cls, api_key: str, datasets_dir: Optional[Path] = None) -> Meta if datasets_dir: config.datasets_dir = datasets_dir return cls(config) + + @property + def team(self) -> TeamMeta: + if self._team is None: + self._team = TeamMeta(self) + return self._team diff --git a/darwin/future/meta/objects/base.py b/darwin/future/meta/objects/base.py new file mode 100644 index 000000000..fcd090c5d --- /dev/null +++ b/darwin/future/meta/objects/base.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from typing import Generic, List, Optional, TypeVar + +from darwin.future.pydantic_base import DefaultDarwin + +R = TypeVar("R", bound=DefaultDarwin) + +class MetaBase(Generic[R]): + def __init__(self, items: Optional[List[R]]=None) -> None: + self._items = items + + def __getitem__(self, index: int) -> R: + if self._items is None: + self._items = [] + return self._items[index] + + def __setitem__(self, index: int, value: R) -> None: + if self._items is None: + self._items = [] + self._items[index] = value + + def __iter__(self) -> MetaBase[R]: + self.n = 0 + return self + + def __next__(self) -> R: + if self._items is None: + self._items = [] + if self.n < len(self._items): + result = self._items[self.n] + self.n += 1 + return result + else: + raise StopIteration + + def __len__(self) -> int: + if self._items is None: + self._items = [] + return len(self._items) \ No newline at end of file diff --git a/darwin/future/meta/objects/dataset.py b/darwin/future/meta/objects/dataset.py index 7680a26e7..4a3d11099 100644 --- a/darwin/future/meta/objects/dataset.py +++ b/darwin/future/meta/objects/dataset.py @@ -1,24 +1,31 @@ from typing import List, Optional, Tuple, Union +from darwin.future.core.client import Client from darwin.future.core.datasets.create_dataset import create_dataset from darwin.future.core.datasets.get_dataset import get_dataset +from darwin.future.core.datasets.list_datasets import list_datasets from darwin.future.core.datasets.remove_dataset import remove_dataset from darwin.future.data_objects.dataset import Dataset from darwin.future.helpers.assertion import assert_is -from darwin.future.meta.client import MetaClient -from darwin.future.meta.queries.dataset import DatasetQuery +from darwin.future.meta.objects.base import MetaBase -class DatasetMeta: - client: MetaClient +class DatasetMeta(MetaBase[Dataset]): + """Dataset Meta object. Facilitates the creation of Query objects, lazy loading of sub fields - def __init__(self, client: MetaClient) -> None: - # TODO: Initialise from chaining within MetaClient - self.client = client + Args: + MetaBase (Dataset): Generic MetaBase object expanded by Dataset core object return type - def datasets(self) -> DatasetQuery: - # TODO: implement - raise NotImplementedError() + Returns: + _type_: DatasetMeta + """ + + client: Client + + def __init__(self, client: Client, datasets: Optional[List[Dataset]] = None) -> None: + # TODO: Initialise from chaining within Client + self.client = client + super().__init__(datasets) def get_dataset_by_id(self) -> Dataset: # TODO: implement @@ -83,7 +90,7 @@ def delete_dataset(self, dataset_id: Union[int, str]) -> Tuple[Optional[List[Exc return exceptions or None, dataset_deleted @staticmethod - def _delete_by_slug(client: MetaClient, slug: str) -> int: + def _delete_by_slug(client: Client, slug: str) -> int: """ (internal) Deletes a dataset by slug @@ -100,7 +107,7 @@ def _delete_by_slug(client: MetaClient, slug: str) -> int: int The dataset deleted """ - assert_is(isinstance(client, MetaClient), "client must be a MetaClient") + assert_is(isinstance(client, Client), "client must be a Core Client") assert_is(isinstance(slug, str), "slug must be a string") dataset = get_dataset(client, slug) @@ -112,13 +119,13 @@ def _delete_by_slug(client: MetaClient, slug: str) -> int: return dataset_deleted @staticmethod - def _delete_by_id(client: MetaClient, dataset_id: int) -> int: + def _delete_by_id(client: Client, dataset_id: int) -> int: """ (internal) Deletes a dataset by id Parameters ---------- - client: MetaClient + client: Client The client to use to make the request dataset_id: int @@ -129,7 +136,7 @@ def _delete_by_id(client: MetaClient, dataset_id: int) -> int: int The dataset deleted """ - assert_is(isinstance(client, MetaClient), "client must be a MetaClient") + assert_is(isinstance(client, Client), "client must be a Client") assert_is(isinstance(dataset_id, int), "dataset_id must be an integer") dataset_deleted = remove_dataset(client, dataset_id) @@ -155,3 +162,9 @@ def _validate_slug(slug: str) -> None: VALID_SLUG_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" assert_is(all(c in VALID_SLUG_CHARS for c in slug_copy), "slug must only contain valid characters") + + def __next__(self) -> Dataset: + if self._items is None: + items, exceptions = list_datasets(self.client) + self._items = items + return super().__next__() diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py new file mode 100644 index 000000000..9e55ef8c6 --- /dev/null +++ b/darwin/future/meta/objects/team.py @@ -0,0 +1,35 @@ +from typing import List, Optional + +from darwin.future.core.client import Client +from darwin.future.data_objects.team import Team, get_team +from darwin.future.helpers.assertion import assert_is +from darwin.future.meta.objects.base import MetaBase +from darwin.future.meta.queries.team_member import TeamMemberQuery + + +class TeamMeta(MetaBase[Team]): + """Team Meta object. Facilitates the creation of Query objects, lazy loading of sub fields like members + unlike other MetaBase objects, does not extend the __next__ function because it is not iterable. This is because + Team is linked to api key and only one team can be returned, but stores a list of teams for consistency. This + does mean however that to access the underlying team object, you must access the first element of the list + team = client.team[0] + + Args: + MetaBase (Team): Generic MetaBase object expanded by Team core object return type + + Returns: + _type_: TeamMeta + """ + + client: Client + + def __init__(self, client: Client, teams: Optional[List[Team]] = None) -> None: + # TODO: Initialise from chaining within Client + self.client = client + if not teams: + teams = [get_team(self.client)] + super().__init__(teams) + + @property + def members(self) -> TeamMemberQuery: + return TeamMemberQuery(self.client) diff --git a/darwin/future/meta/objects/team_member.py b/darwin/future/meta/objects/team_member.py new file mode 100644 index 000000000..e7cbf705e --- /dev/null +++ b/darwin/future/meta/objects/team_member.py @@ -0,0 +1,21 @@ +from typing import List, Optional + +from darwin.future.core.client import Client +from darwin.future.data_objects.team import TeamMember, get_team_members +from darwin.future.meta.objects.base import MetaBase + + +class TeamMembersMeta(MetaBase[TeamMember]): + client: Client + + def __init__(self, client: Client, members: Optional[List[TeamMember]]=None) -> None: + # TODO: Initialise from chaining within MetaClient + self.client = client + super().__init__(members) + + def __next__(self) -> TeamMember: + if self._items is None: + items, exceptions = get_team_members(self.client) + self._items = items + return super().__next__() + \ No newline at end of file diff --git a/darwin/future/meta/queries/dataset.py b/darwin/future/meta/queries/dataset.py index 5471059dd..e1946a50d 100644 --- a/darwin/future/meta/queries/dataset.py +++ b/darwin/future/meta/queries/dataset.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List from darwin.future.core.client import Client @@ -5,9 +7,10 @@ from darwin.future.core.types.query import Modifier, Param, Query, QueryFilter from darwin.future.data_objects.dataset import Dataset from darwin.future.data_objects.release import ReleaseList +from darwin.future.meta.objects.dataset import DatasetMeta -class DatasetQuery(Query[Dataset]): +class DatasetQuery(Query[DatasetMeta, Dataset]): """ DatasetQuery object with methods to manage filters, retrieve data, and execute filters @@ -19,25 +22,25 @@ class DatasetQuery(Query[Dataset]): collect: Executes the query and returns the filtered data """ - def where(self, param: Param) -> "DatasetQuery": + def where(self, param: Param) -> DatasetQuery: filter = QueryFilter.parse_obj(param) query = self + filter - return DatasetQuery(query.filters) + return DatasetQuery(self.client, query.filters) - def collect(self, client: Client) -> List[Dataset]: - datasets, exceptions = list_datasets(client) + def collect(self) -> DatasetMeta: + datasets, exceptions = list_datasets(self.client) if exceptions: # TODO: print and or raise exceptions, tbd how we want to handle this pass if not self.filters: - return datasets + self.filters = [] for filter in self.filters: datasets = self._execute_filters(datasets, filter) - - return datasets + meta = DatasetMeta(self.client, datasets) + return meta def _execute_filters(self, datasets: List[Dataset], filter: QueryFilter) -> List[Dataset]: """Executes filtering on the local list of datasets, applying special logic for role filtering diff --git a/darwin/future/meta/queries/team_member.py b/darwin/future/meta/queries/team_member.py index de376f3f3..54987b949 100644 --- a/darwin/future/meta/queries/team_member.py +++ b/darwin/future/meta/queries/team_member.py @@ -2,13 +2,13 @@ from typing import List -from darwin.future.core.backend import get_team_members from darwin.future.core.client import Client from darwin.future.core.types.query import Param, Query, QueryFilter -from darwin.future.data_objects.team import TeamMember +from darwin.future.data_objects.team import TeamMember, get_team_members +from darwin.future.meta.objects.team_member import TeamMembersMeta -class TeamMemberQuery(Query[TeamMember]): +class TeamMemberQuery(Query[TeamMembersMeta, TeamMember]): """TeamMemberQuery object with methods to manage filters, retrieve data, and execute filters Methods: where: Adds a filter to the query @@ -20,18 +20,18 @@ def where(self, param: Param) -> TeamMemberQuery: filter = QueryFilter.parse_obj(param) query = self + filter - return TeamMemberQuery(query.filters) + return TeamMemberQuery(self.client, query.filters) - def collect(self, client: Client) -> List[TeamMember]: - members, exceptions = get_team_members(client) + def collect(self) -> TeamMembersMeta: + members, exceptions = get_team_members(self.client) if exceptions: # TODO: print and or raise exceptions, tbd how we want to handle this pass if not self.filters: - return members + self.filters = [] for filter in self.filters: members = self._execute_filter(members, filter) - return members + return TeamMembersMeta(self.client, members) def _execute_filter(self, members: List[TeamMember], filter: QueryFilter) -> List[TeamMember]: """Executes filtering on the local list of members, applying special logic for role filtering diff --git a/darwin/future/tests/core/test_query.py b/darwin/future/tests/core/test_query.py index ebf60a22d..d9ca6b6bf 100644 --- a/darwin/future/tests/core/test_query.py +++ b/darwin/future/tests/core/test_query.py @@ -3,8 +3,10 @@ import pytest +from darwin.future.core.client import Client from darwin.future.core.types import query as Query from darwin.future.data_objects.team import Team +from darwin.future.tests.core.fixtures import * @pytest.fixture @@ -31,13 +33,13 @@ def test_team() -> Team: return Team(slug="test-team", id=0) -def test_query_instantiated(basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: - q = non_abc_query(basic_filters) +def test_query_instantiated(base_client: Client, basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: + q = non_abc_query(base_client, basic_filters) assert q.filters == basic_filters -def test_query_filter_functionality(basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: - q = non_abc_query() +def test_query_filter_functionality(base_client: Client, basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: + q = non_abc_query(base_client) for f in basic_filters: q = q.filter(f) assert q.filters == basic_filters @@ -57,13 +59,6 @@ def test_query_filter_functionality(basic_filters: List[Query.QueryFilter], non_ assert q.filters == basic_filters -def test_query_iterable(basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: - q = non_abc_query(basic_filters) - for i, f in enumerate(q): - assert f == basic_filters[i] - assert q.filters is not None - assert len(q) == len(basic_filters) - @pytest.mark.parametrize( "mod,param,check,expected", diff --git a/darwin/future/tests/data_objects/fixtures.py b/darwin/future/tests/data_objects/fixtures.py new file mode 100644 index 000000000..2817351c3 --- /dev/null +++ b/darwin/future/tests/data_objects/fixtures.py @@ -0,0 +1,30 @@ +import pytest + + +@pytest.fixture +def basic_team() -> dict: + return {"slug": "test-team", "id": 0} + + +@pytest.fixture +def basic_dataset() -> dict: + return {"name": "test-dataset", "slug": "test-dataset"} + + +@pytest.fixture +def basic_release() -> dict: + return {"name": "test-release"} + + +@pytest.fixture +def basic_combined(basic_team: dict, basic_dataset: dict, basic_release: dict) -> dict: + combined = basic_team + combined["datasets"] = [basic_dataset] + combined["datasets"][0]["releases"] = [basic_release] + return combined + + +@pytest.fixture +def broken_combined(basic_combined: dict) -> dict: + del basic_combined["datasets"][0]["name"] + return basic_combined diff --git a/darwin/future/tests/data_objects/test_darwin_meta.py b/darwin/future/tests/data_objects/test_general_darwin_objects.py similarity index 59% rename from darwin/future/tests/data_objects/test_darwin_meta.py rename to darwin/future/tests/data_objects/test_general_darwin_objects.py index eff5db650..575c2769b 100644 --- a/darwin/future/tests/data_objects/test_darwin_meta.py +++ b/darwin/future/tests/data_objects/test_general_darwin_objects.py @@ -6,35 +6,7 @@ from darwin.future.data_objects.dataset import Dataset from darwin.future.data_objects.release import Release from darwin.future.data_objects.team import Team - - -@pytest.fixture -def basic_team() -> dict: - return {"slug": "test-team", "id": 0} - - -@pytest.fixture -def basic_dataset() -> dict: - return {"name": "test-dataset", "slug": "test-dataset"} - - -@pytest.fixture -def basic_release() -> dict: - return {"name": "test-release"} - - -@pytest.fixture -def basic_combined(basic_team: dict, basic_dataset: dict, basic_release: dict) -> dict: - combined = basic_team - combined["datasets"] = [basic_dataset] - combined["datasets"][0]["releases"] = [basic_release] - return combined - - -@pytest.fixture -def broken_combined(basic_combined: dict) -> dict: - del basic_combined["datasets"][0]["name"] - return basic_combined +from darwin.future.tests.data_objects.fixtures import * def test_integrated_parsing_works_with_raw(basic_combined: dict) -> None: diff --git a/darwin/future/tests/core/test_backend.py b/darwin/future/tests/data_objects/test_team.py similarity index 73% rename from darwin/future/tests/core/test_backend.py rename to darwin/future/tests/data_objects/test_team.py index 6287c4b8c..b06464d34 100644 --- a/darwin/future/tests/core/test_backend.py +++ b/darwin/future/tests/data_objects/test_team.py @@ -4,9 +4,8 @@ import responses from pydantic import ValidationError -from darwin.future.core import backend as be from darwin.future.core.client import Client -from darwin.future.data_objects.team import Team, TeamMember +from darwin.future.data_objects.team import Team, TeamMember, get_team, get_team_members from darwin.future.tests.core.fixtures import * from darwin.future.tests.fixtures import * @@ -17,7 +16,7 @@ def test_get_team_returns_valid_team(base_client: Client, base_team_json: dict, with responses.RequestsMock() as rsps: rsps.add(responses.GET, endpoint, json=base_team_json) - team = be.get_team(base_client, slug) + team = get_team(base_client, slug) assert team == base_team @@ -28,7 +27,7 @@ def test_get_team_fails_on_incorrect_input(base_client: Client, base_team: Team) rsps.add(responses.GET, endpoint, json={}) with pytest.raises(ValidationError): - team = be.get_team(base_client, slug) + team = get_team(base_client, slug) def test_get_team_members_returns_valid_list(base_client: Client, base_team_member_json: dict) -> None: @@ -37,7 +36,7 @@ def test_get_team_members_returns_valid_list(base_client: Client, base_team_memb with responses.RequestsMock() as rsps: rsps.add(responses.GET, endpoint, json=[base_team_member_json, base_team_member_json]) - members, errors = be.get_team_members(base_client) + members, errors = get_team_members(base_client) assert len(members) == 2 assert len(errors) == 0 assert members == synthetic_list @@ -48,8 +47,20 @@ def test_get_team_members_fails_on_incorrect_input(base_client: Client, base_tea with responses.RequestsMock() as rsps: rsps.add(responses.GET, endpoint, json=[base_team_member_json, {}]) - members, errors = be.get_team_members(base_client) + members, errors = get_team_members(base_client) assert len(members) == 1 assert len(errors) == 1 assert isinstance(errors[0], ValidationError) assert isinstance(members[0], TeamMember) + + +def test_team_from_client(base_client: Client, base_team_json: dict, base_team: Team) -> None: + with responses.RequestsMock() as rsps: + rsps.add( + responses.GET, + base_client.config.api_endpoint + f"teams/{base_client.config.default_team}", + json=base_team_json, + ) + + team = Team.from_client(base_client) + assert team == base_team diff --git a/darwin/future/tests/data_objects/workflow/test_wfstage.py b/darwin/future/tests/data_objects/workflow/test_wfstage.py index f841044ec..d36ef614f 100644 --- a/darwin/future/tests/data_objects/workflow/test_wfstage.py +++ b/darwin/future/tests/data_objects/workflow/test_wfstage.py @@ -1,6 +1,5 @@ from json import loads from pathlib import Path -from tkinter import W from uuid import UUID import pytest diff --git a/darwin/future/tests/meta/fixtures.py b/darwin/future/tests/meta/fixtures.py index e69de29bb..c0e3b1af6 100644 --- a/darwin/future/tests/meta/fixtures.py +++ b/darwin/future/tests/meta/fixtures.py @@ -0,0 +1,10 @@ +from pytest import fixture, raises + +from darwin.future.core.client import DarwinConfig +from darwin.future.meta.client import MetaClient +from darwin.future.tests.core.fixtures import * + + +@fixture +def base_meta_client(base_config: DarwinConfig) -> MetaClient: + return MetaClient(base_config) \ No newline at end of file diff --git a/darwin/future/tests/meta/objects/fixtures.py b/darwin/future/tests/meta/objects/fixtures.py new file mode 100644 index 000000000..83ce6e715 --- /dev/null +++ b/darwin/future/tests/meta/objects/fixtures.py @@ -0,0 +1,10 @@ +from pytest import fixture, raises + +from darwin.future.core.client import Client, DarwinConfig +from darwin.future.data_objects.team import Team +from darwin.future.meta.objects.team import TeamMeta + + +@fixture +def base_meta_team(base_client: Client, base_team: Team) -> TeamMeta: + return TeamMeta(base_client, [base_team]) diff --git a/darwin/future/tests/meta/objects/test_teammeta.py b/darwin/future/tests/meta/objects/test_teammeta.py new file mode 100644 index 000000000..55ad1dac6 --- /dev/null +++ b/darwin/future/tests/meta/objects/test_teammeta.py @@ -0,0 +1,20 @@ +from unittest.mock import Mock, patch + +import responses +from pytest import fixture, raises + +from darwin.future.core.client import Client, DarwinConfig +from darwin.future.data_objects.team import Team, TeamMember +from darwin.future.meta.objects.team import TeamMeta +from darwin.future.tests.core.fixtures import * +from darwin.future.tests.meta.objects.fixtures import * + + +def test_team_meta_collects_members(base_meta_team: TeamMeta, base_client: Client, base_team_member: TeamMember, base_team_member_json: dict) -> None: + + with responses.RequestsMock() as rsps: + endpoint = base_client.config.api_endpoint + "memberships" + rsps.add(responses.GET, endpoint, json=[base_team_member_json]) + members = base_meta_team.members.collect() + assert len(members) == 1 + assert members[0] == base_team_member \ No newline at end of file diff --git a/darwin/future/tests/meta/queries/test_dataset.py b/darwin/future/tests/meta/queries/test_dataset.py index 6e3b3ccb8..4d5944e3f 100644 --- a/darwin/future/tests/meta/queries/test_dataset.py +++ b/darwin/future/tests/meta/queries/test_dataset.py @@ -1,29 +1,28 @@ -from pytest import fixture, mark import responses +from pytest import fixture, mark + from darwin.future.core.client import Client from darwin.future.data_objects.dataset import Dataset - from darwin.future.meta.queries.dataset import DatasetQuery from darwin.future.tests.core.fixtures import * def test_dataset_collects_basic(base_client: Client, base_datasets_json: dict) -> None: - query = DatasetQuery() + query = DatasetQuery(base_client) with responses.RequestsMock() as rsps: endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json) - datasets = query.collect(base_client) - + datasets = query.collect() assert len(datasets) == 2 assert all([isinstance(dataset, Dataset) for dataset in datasets]) def test_datasetquery_only_passes_back_correctly_formed_objects(base_client: Client, base_dataset_json: dict) -> None: - query = DatasetQuery() + query = DatasetQuery(base_client) with responses.RequestsMock() as rsps: endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=[base_dataset_json, {}]) - datasets = query.collect(base_client) + datasets = query.collect() assert len(datasets) == 1 assert isinstance(datasets[0], Dataset) @@ -31,10 +30,10 @@ def test_datasetquery_only_passes_back_correctly_formed_objects(base_client: Cli def test_dataset_filters_name(base_client: Client, base_datasets_json: dict) -> None: with responses.RequestsMock() as rsps: - query = DatasetQuery().where({"name": "name", "param": "test dataset 1"}) + query = DatasetQuery(base_client).where({"name": "name", "param": "test dataset 1"}) endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json) - datasets = query.collect(base_client) + datasets = query.collect() assert len(datasets) == 1 assert datasets[0].slug == "test-dataset-1" @@ -42,10 +41,10 @@ def test_dataset_filters_name(base_client: Client, base_datasets_json: dict) -> def test_dataset_filters_id(base_client: Client, base_datasets_json: dict) -> None: with responses.RequestsMock() as rsps: - query = DatasetQuery().where({"name": "id", "param": 1}) + query = DatasetQuery(base_client).where({"name": "id", "param": 1}) endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json) - datasets = query.collect(base_client) + datasets = query.collect() assert len(datasets) == 1 assert datasets[0].slug == "test-dataset-1" @@ -53,10 +52,10 @@ def test_dataset_filters_id(base_client: Client, base_datasets_json: dict) -> No def test_dataset_filters_slug(base_client: Client, base_datasets_json: dict) -> None: with responses.RequestsMock() as rsps: - query = DatasetQuery().where({"name": "slug", "param": "test-dataset-1"}) + query = DatasetQuery(base_client).where({"name": "slug", "param": "test-dataset-1"}) endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json) - datasets = query.collect(base_client) + datasets = query.collect() assert len(datasets) == 1 assert datasets[0].slug == "test-dataset-1" @@ -64,18 +63,18 @@ def test_dataset_filters_slug(base_client: Client, base_datasets_json: dict) -> def test_dataset_filters_releases(base_client: Client, base_datasets_json_with_releases: dict) -> None: with responses.RequestsMock() as rsps: - query = DatasetQuery().where({"name": "releases", "param": "release1"}) + query = DatasetQuery(base_client).where({"name": "releases", "param": "release1"}) endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json_with_releases) - datasets_odd_ids = query.collect(base_client) + datasets_odd_ids = query.collect() assert len(datasets_odd_ids) == 2 assert datasets_odd_ids[0].slug == "test-dataset-1" assert datasets_odd_ids[1].slug == "test-dataset-3" - query2 = DatasetQuery().where({"name": "releases", "param": "release2"}) - datasets_even_ids = query2.collect(base_client) + query2 = DatasetQuery(base_client).where({"name": "releases", "param": "release2"}) + datasets_even_ids = query2.collect() assert len(datasets_even_ids) == 2 assert datasets_even_ids[0].slug == "test-dataset-2" diff --git a/darwin/future/tests/meta/queries/test_team_member.py b/darwin/future/tests/meta/queries/test_team_member.py index 71a17eaed..849326579 100644 --- a/darwin/future/tests/meta/queries/test_team_member.py +++ b/darwin/future/tests/meta/queries/test_team_member.py @@ -11,21 +11,21 @@ def test_team_member_collects_basic(base_client: Client, base_team_members_json: List[dict]) -> None: - query = TeamMemberQuery() + query = TeamMemberQuery(base_client) with responses.RequestsMock() as rsps: endpoint = base_client.config.api_endpoint + "memberships" rsps.add(responses.GET, endpoint, json=base_team_members_json) - members = query.collect(base_client) + members = query.collect() assert len(members) == len(TeamMemberRole) assert isinstance(members[0], TeamMember) def test_team_member_only_passes_back_correct(base_client: Client, base_team_member_json: dict) -> None: - query = TeamMemberQuery() + query = TeamMemberQuery(base_client) with responses.RequestsMock() as rsps: endpoint = base_client.config.api_endpoint + "memberships" rsps.add(responses.GET, endpoint, json=[base_team_member_json, {}]) - members = query.collect(base_client) + members = query.collect() assert len(members) == 1 assert isinstance(members[0], TeamMember) @@ -36,18 +36,18 @@ def test_team_member_filters_role( ) -> None: with responses.RequestsMock() as rsps: # Test equal - query = TeamMemberQuery().where({"name": "role", "param": role.value}) + query = TeamMemberQuery(base_client).where({"name": "role", "param": role.value}) endpoint = base_client.config.api_endpoint + "memberships" rsps.add(responses.GET, endpoint, json=base_team_members_json) - members = query.collect(base_client) + members = query.collect() assert len(members) == 1 assert members[0].role == role # Test not equal rsps.reset() - query = TeamMemberQuery().where({"name": "role", "param": role.value, "modifier": "!="}) + query = TeamMemberQuery(base_client).where({"name": "role", "param": role.value, "modifier": "!="}) rsps.add(responses.GET, endpoint, json=base_team_members_json) - members = query.collect(base_client) + members = query.collect() assert len(members) == len(TeamMemberRole) - 1 assert role not in [member.role for member in members] @@ -57,10 +57,10 @@ def test_team_member_filters_general(base_client: Client, base_team_members_json base_team_members_json[idx]["id"] = idx + 1 with responses.RequestsMock() as rsps: - query = TeamMemberQuery().where({"name": "id", "param": 1}) + query = TeamMemberQuery(base_client).where({"name": "id", "param": 1}) endpoint = base_client.config.api_endpoint + "memberships" rsps.add(responses.GET, endpoint, json=base_team_members_json) - members = query.collect(base_client) + members = query.collect() assert len(members) == 1 assert members[0].id == 1 @@ -70,10 +70,10 @@ def test_team_member_filters_general(base_client: Client, base_team_members_json rsps.add(responses.GET, endpoint, json=base_team_members_json) members = ( - TeamMemberQuery() + TeamMemberQuery(base_client) .where({"name": "id", "param": 1, "modifier": ">"}) .where({"name": "id", "param": len(base_team_members_json), "modifier": "<"}) - .collect(base_client) + .collect() ) assert len(members) == len(base_team_members_json) - 2 diff --git a/darwin/future/tests/meta/test_client.py b/darwin/future/tests/meta/test_client.py index 2b5799bd2..9e6ac1d84 100644 --- a/darwin/future/tests/meta/test_client.py +++ b/darwin/future/tests/meta/test_client.py @@ -4,7 +4,11 @@ import responses from darwin.future.core.client import DarwinConfig +from darwin.future.data_objects.team import Team from darwin.future.meta.client import MetaClient +from darwin.future.meta.objects.team import TeamMeta +from darwin.future.tests.core.fixtures import * +from darwin.future.tests.meta.fixtures import * def test_creates_from_api_key() -> None: @@ -13,3 +17,13 @@ def test_creates_from_api_key() -> None: rsps.add(responses.GET, base_api_endpoint + "users/token_info", json={"selected_team": {"slug": "test-team"}}) client = MetaClient.from_api_key(api_key="test") assert client.config.default_team == "test-team" + + +def test_team_property(base_meta_client: MetaClient, base_team: Team, base_team_json: dict) -> None: + client = base_meta_client + endpoint = client.config.api_endpoint + f"teams/{client.config.default_team}" + with responses.RequestsMock() as rsps: + rsps.add(responses.GET, endpoint, json=base_team_json) + team = client.team + assert isinstance(team, TeamMeta) + assert team[0] == base_team \ No newline at end of file