From 37b430735faa826882ab8c2ec6002464f2f37bbe Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Fri, 12 May 2023 11:54:33 +0100 Subject: [PATCH 1/6] refactor of backend util functions - Circular imports prevention --- darwin/future/core/backend.py | 24 -------------- darwin/future/data_objects/team.py | 33 ++++++++++++++++++- darwin/future/meta/queries/team_member.py | 3 +- darwin/future/tests/core/fixtures.py | 3 +- darwin/future/tests/data_objects/fixtures.py | 30 +++++++++++++++++ ...meta.py => test_general_darwin_objects.py} | 30 +---------------- .../test_team.py} | 23 +++++++++---- 7 files changed, 83 insertions(+), 63 deletions(-) delete mode 100644 darwin/future/core/backend.py create mode 100644 darwin/future/tests/data_objects/fixtures.py rename darwin/future/tests/data_objects/{test_darwin_meta.py => test_general_darwin_objects.py} (59%) rename darwin/future/tests/{core/test_backend.py => data_objects/test_team.py} (73%) diff --git a/darwin/future/core/backend.py b/darwin/future/core/backend.py deleted file mode 100644 index 74a07cc15..000000000 --- a/darwin/future/core/backend.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import List, Optional, Tuple - -from darwin.future.core.client import Client -from darwin.future.data_objects.team import Team, TeamMember - - -def get_team(client: Client, team_slug: Optional[str] = None) -> Team: - """Returns the team 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]]: - 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/data_objects/team.py b/darwin/future/data_objects/team.py index 3e3cd403d..a5d748823 100644 --- a/darwin/future/data_objects/team.py +++ b/darwin/future/data_objects/team.py @@ -1,7 +1,11 @@ -from typing import List, Optional +from __future__ import annotations + +from typing import List, Optional, Tuple from pydantic import validator +# from darwin.future.core.backend import get_team +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 +55,32 @@ 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""" + 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""" + 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]]: + 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/queries/team_member.py b/darwin/future/meta/queries/team_member.py index a7cd0b9a7..03de0c56d 100644 --- a/darwin/future/meta/queries/team_member.py +++ b/darwin/future/meta/queries/team_member.py @@ -2,10 +2,9 @@ from typing import Any, Dict, List -from darwin.future.core.backend import get_team_members from darwin.future.core.client import Client from darwin.future.core.types.query import Query, QueryFilter -from darwin.future.data_objects.team import TeamMember +from darwin.future.data_objects.team import TeamMember, get_team_members Param = Dict[str, Any] # type: ignore diff --git a/darwin/future/tests/core/fixtures.py b/darwin/future/tests/core/fixtures.py index c1d7d968e..3a6dc1e8e 100644 --- a/darwin/future/tests/core/fixtures.py +++ b/darwin/future/tests/core/fixtures.py @@ -3,7 +3,8 @@ import pytest from darwin.future.core.client import Client, DarwinConfig -from darwin.future.data_objects.team import Team, TeamMember, TeamMemberRole +from darwin.future.data_objects.team import Team, TeamMember +from darwin.future.data_objects.team_member_role import TeamMemberRole @pytest.fixture 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 From e506d1b925309e753bb29102f8edae8ed33c1613 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 26 Jun 2023 10:14:09 +0100 Subject: [PATCH 2/6] client changes for lazy loading --- darwin/future/core/types/query.py | 28 +++------------- darwin/future/meta/client.py | 4 +++ darwin/future/meta/objects/team.py | 17 ++++++++++ darwin/future/meta/queries/dataset.py | 6 ++-- darwin/future/meta/queries/team_member.py | 6 ++-- darwin/future/tests/core/test_query.py | 14 ++++---- .../future/tests/meta/queries/test_dataset.py | 32 +++++++++---------- .../tests/meta/queries/test_team_member.py | 24 +++++++------- 8 files changed, 68 insertions(+), 63 deletions(-) create mode 100644 darwin/future/meta/objects/team.py diff --git a/darwin/future/core/types/query.py b/darwin/future/core/types/query.py index 392628e52..195b8b435 100644 --- a/darwin/future/core/types/query.py +++ b/darwin/future/core/types/query.py @@ -64,7 +64,8 @@ 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 def filter(self, filter: QueryFilter) -> Query[T]: @@ -75,14 +76,14 @@ def __add__(self, filter: QueryFilter) -> Query[T]: 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]: 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]: assert filter is not None @@ -125,28 +126,9 @@ def where(self, param: Param) -> Query[T]: raise NotImplementedError("Not implemented") @abstractmethod - def collect(self, client: Client) -> List[T]: + def collect(self) -> List[T]: raise NotImplementedError("Not implemented") def _generic_execute_filter(self, objects: List[T], filter: QueryFilter) -> List[T]: 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/meta/client.py b/darwin/future/meta/client.py index a5f896138..44024ddc7 100644 --- a/darwin/future/meta/client.py +++ b/darwin/future/meta/client.py @@ -27,3 +27,7 @@ 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) -> str: + return self.config.default_team \ No newline at end of file diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py new file mode 100644 index 000000000..37ca19350 --- /dev/null +++ b/darwin/future/meta/objects/team.py @@ -0,0 +1,17 @@ +from typing import List, Optional, Tuple, Union + +from darwin.future.helpers.assertion import assert_is +from darwin.future.meta.client import MetaClient +from darwin.future.meta.queries.team_member import TeamMemberQuery + + +class DatasetMeta: + client: MetaClient + + def __init__(self, client: MetaClient) -> None: + # TODO: Initialise from chaining within MetaClient + self.client = client + + @property + def members(self) -> TeamMemberQuery: + return TeamMemberQuery(self.client) \ No newline at end of file diff --git a/darwin/future/meta/queries/dataset.py b/darwin/future/meta/queries/dataset.py index 5471059dd..f72351015 100644 --- a/darwin/future/meta/queries/dataset.py +++ b/darwin/future/meta/queries/dataset.py @@ -23,10 +23,10 @@ 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) -> List[Dataset]: + datasets, exceptions = list_datasets(self.client) if exceptions: # TODO: print and or raise exceptions, tbd how we want to handle this pass diff --git a/darwin/future/meta/queries/team_member.py b/darwin/future/meta/queries/team_member.py index 351658eda..3f66b8742 100644 --- a/darwin/future/meta/queries/team_member.py +++ b/darwin/future/meta/queries/team_member.py @@ -19,10 +19,10 @@ 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) -> List[TeamMember]: + members, exceptions = get_team_members(self.client) if exceptions: # TODO: print and or raise exceptions, tbd how we want to handle this pass diff --git a/darwin/future/tests/core/test_query.py b/darwin/future/tests/core/test_query.py index ebf60a22d..abb1a1f92 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,8 +59,8 @@ 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) +def test_query_iterable(base_client: Client, basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: + q = non_abc_query(base_client, basic_filters) for i, f in enumerate(q): assert f == basic_filters[i] assert q.filters is not None diff --git a/darwin/future/tests/meta/queries/test_dataset.py b/darwin/future/tests/meta/queries/test_dataset.py index 6e3b3ccb8..4cab30d1e 100644 --- a/darwin/future/tests/meta/queries/test_dataset.py +++ b/darwin/future/tests/meta/queries/test_dataset.py @@ -1,29 +1,29 @@ -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 +31,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 +42,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 +53,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 +64,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 From 484a8acb9d0fbd753b90358f9703b6ef0a52889d Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 26 Jun 2023 17:33:16 +0100 Subject: [PATCH 3/6] WIP: refactor for generics --- .vscode/settings.json | 3 +- darwin/future/core/types/query.py | 47 ++++++++++++------- darwin/future/meta/objects/base.py | 35 ++++++++++++++ darwin/future/meta/objects/dataset.py | 31 +++++++----- darwin/future/meta/objects/team.py | 10 ++-- darwin/future/meta/objects/team_member.py | 34 ++++++++++++++ darwin/future/meta/queries/dataset.py | 13 +++-- darwin/future/meta/queries/team_member.py | 9 ++-- .../tests/meta/objects/test_teammeta.py | 20 ++++++++ 9 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 darwin/future/meta/objects/base.py create mode 100644 darwin/future/meta/objects/team_member.py create mode 100644 darwin/future/tests/meta/objects/test_teammeta.py 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 195b8b435..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 @@ -67,25 +69,26 @@ class Query(Generic[T], ABC): 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.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__(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: @@ -94,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: @@ -107,28 +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) -> 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))] diff --git a/darwin/future/meta/objects/base.py b/darwin/future/meta/objects/base.py new file mode 100644 index 000000000..2618dd756 --- /dev/null +++ b/darwin/future/meta/objects/base.py @@ -0,0 +1,35 @@ +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) -> R: + if self._items is None: + self._items = [] + self.n = 0 + return self._items[self.n] + + 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 \ No newline at end of file diff --git a/darwin/future/meta/objects/dataset.py b/darwin/future/meta/objects/dataset.py index a49ad63b6..193014720 100644 --- a/darwin/future/meta/objects/dataset.py +++ b/darwin/future/meta/objects/dataset.py @@ -1,23 +1,22 @@ from typing import List, Optional, Tuple, Union +from darwin.future.core.client import Client 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]): + client: Client - def __init__(self, client: MetaClient) -> None: + def __init__(self, client: Client, datasets: Optional[List[Dataset]]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client - - def datasets(self) -> DatasetQuery: - # TODO: implement - raise NotImplementedError() + self.datasets = datasets + def get_dataset_by_id(self) -> Dataset: # TODO: implement @@ -47,8 +46,8 @@ 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: - assert_is(isinstance(client, MetaClient), "client must be a MetaClient") + def _delete_by_slug(client: Client, slug: str) -> int: + assert_is(isinstance(client, Client), "client must be a MetaClient") assert_is(isinstance(slug, str), "slug must be a string") dataset = get_dataset(client, slug) @@ -60,9 +59,15 @@ def _delete_by_slug(client: MetaClient, slug: str) -> int: return dataset_deleted @staticmethod - def _delete_by_id(client: MetaClient, dataset_id: int) -> int: - assert_is(isinstance(client, MetaClient), "client must be a MetaClient") + def _delete_by_id(client: Client, dataset_id: int) -> int: + assert_is(isinstance(client, Client), "client must be a MetaClient") assert_is(isinstance(dataset_id, int), "dataset_id must be an integer") dataset_deleted = remove_dataset(client, dataset_id) return dataset_deleted + + + def __next__(self) -> Dataset: + if self._items is None: + self._items = list_datasets(self.client) + return super().__next__() \ No newline at end of file diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py index 37ca19350..438e1a729 100644 --- a/darwin/future/meta/objects/team.py +++ b/darwin/future/meta/objects/team.py @@ -1,16 +1,20 @@ -from typing import List, Optional, Tuple, Union +from typing import List, Optional +from darwin.future.data_objects.team import Team from darwin.future.helpers.assertion import assert_is from darwin.future.meta.client import MetaClient +from darwin.future.meta.objects.base import MetaBase from darwin.future.meta.queries.team_member import TeamMemberQuery -class DatasetMeta: +class TeamMeta(MetaBase[Team]): client: MetaClient - def __init__(self, client: MetaClient) -> None: + def __init__(self, client: MetaClient, team: Optional[Team]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client + self.team = team + @property def members(self) -> TeamMemberQuery: diff --git a/darwin/future/meta/objects/team_member.py b/darwin/future/meta/objects/team_member.py new file mode 100644 index 000000000..6f1ca25e2 --- /dev/null +++ b/darwin/future/meta/objects/team_member.py @@ -0,0 +1,34 @@ +from typing import List, Optional + +from darwin.future.core.client import Client +from darwin.future.data_objects.team import TeamMember, get_team, 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 + self.members = members + + def __next__(self) -> TeamMember: + if self.members is None: + self.members = get_team_members(self.client) + if self.n < len(self.members): + result = self.members[self.n] + self.n += 1 + return result + else: + raise StopIteration + + def __len__(self) -> int: + if self.members is None: + self.members = get_team_members(self.client) + return len(self.members) + + def __getitem__(self, index: int) -> TeamMember: + if self.members is None: + self.members = get_team_members(self.client) + return self.members[index] \ No newline at end of file diff --git a/darwin/future/meta/queries/dataset.py b/darwin/future/meta/queries/dataset.py index f72351015..2ef3df3d4 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,14 +22,14 @@ 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(self.client, query.filters) + return DatasetQuery(query.filters) - def collect(self) -> List[Dataset]: - datasets, exceptions = list_datasets(self.client) + def collect(self, client: Client) -> List[Dataset]: + datasets, exceptions = list_datasets(client) if exceptions: # TODO: print and or raise exceptions, tbd how we want to handle this pass diff --git a/darwin/future/meta/queries/team_member.py b/darwin/future/meta/queries/team_member.py index 3f66b8742..54987b949 100644 --- a/darwin/future/meta/queries/team_member.py +++ b/darwin/future/meta/queries/team_member.py @@ -5,9 +5,10 @@ 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, 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 @@ -21,16 +22,16 @@ def where(self, param: Param) -> TeamMemberQuery: return TeamMemberQuery(self.client, query.filters) - def collect(self) -> List[TeamMember]: + 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/meta/objects/test_teammeta.py b/darwin/future/tests/meta/objects/test_teammeta.py new file mode 100644 index 000000000..721634ceb --- /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 DarwinConfig +from darwin.future.meta.client import MetaClient +from darwin.future.meta.objects.team import TeamMeta +from darwin.future.tests.core.fixtures import * + + +def test_team_meta_collects_basic(base_client: Client, base_teams_json: dict) -> None: + query = TeamMeta(base_client) + with responses.RequestsMock() as rsps: + endpoint = base_client.config.api_endpoint + "teams" + rsps.add(responses.GET, endpoint, json=base_teams_json) + teams = query.collect() + + assert len(teams) == 2 + assert all([isinstance(team, Team) for team in teams]) \ No newline at end of file From ce7da8f754b209f2200eb59a8624d6d45a7b9e81 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 26 Jun 2023 18:21:04 +0100 Subject: [PATCH 4/6] refactor for lazy loading --- darwin/future/meta/objects/base.py | 15 +++++++---- darwin/future/meta/objects/dataset.py | 5 ++-- darwin/future/meta/objects/team.py | 8 +++--- darwin/future/meta/objects/team_member.py | 27 +++++-------------- darwin/future/meta/queries/dataset.py | 12 ++++----- darwin/future/tests/core/test_query.py | 7 ----- .../tests/meta/objects/test_teammeta.py | 19 +++++++------ .../future/tests/meta/queries/test_dataset.py | 1 - 8 files changed, 39 insertions(+), 55 deletions(-) diff --git a/darwin/future/meta/objects/base.py b/darwin/future/meta/objects/base.py index 2618dd756..fcd090c5d 100644 --- a/darwin/future/meta/objects/base.py +++ b/darwin/future/meta/objects/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Generic, List, Optional, TypeVar from darwin.future.pydantic_base import DefaultDarwin @@ -18,11 +20,9 @@ def __setitem__(self, index: int, value: R) -> None: self._items = [] self._items[index] = value - def __iter__(self) -> R: - if self._items is None: - self._items = [] + def __iter__(self) -> MetaBase[R]: self.n = 0 - return self._items[self.n] + return self def __next__(self) -> R: if self._items is None: @@ -32,4 +32,9 @@ def __next__(self) -> R: self.n += 1 return result else: - raise StopIteration \ No newline at end of file + 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 193014720..c5e2562ed 100644 --- a/darwin/future/meta/objects/dataset.py +++ b/darwin/future/meta/objects/dataset.py @@ -15,7 +15,7 @@ class DatasetMeta(MetaBase[Dataset]): def __init__(self, client: Client, datasets: Optional[List[Dataset]]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client - self.datasets = datasets + super().__init__(datasets) def get_dataset_by_id(self) -> Dataset: @@ -69,5 +69,6 @@ def _delete_by_id(client: Client, dataset_id: int) -> int: def __next__(self) -> Dataset: if self._items is None: - self._items = list_datasets(self.client) + items, exceptions = list_datasets(self.client) + self._items = items return super().__next__() \ No newline at end of file diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py index 438e1a729..e6a83f7a6 100644 --- a/darwin/future/meta/objects/team.py +++ b/darwin/future/meta/objects/team.py @@ -1,19 +1,19 @@ from typing import List, Optional +from darwin.future.core.client import Client from darwin.future.data_objects.team import Team from darwin.future.helpers.assertion import assert_is -from darwin.future.meta.client import MetaClient from darwin.future.meta.objects.base import MetaBase from darwin.future.meta.queries.team_member import TeamMemberQuery class TeamMeta(MetaBase[Team]): - client: MetaClient + client: Client - def __init__(self, client: MetaClient, team: Optional[Team]=None) -> None: + def __init__(self, client: Client, teams: Optional[List[Team]]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client - self.team = team + super().__init__(teams) @property diff --git a/darwin/future/meta/objects/team_member.py b/darwin/future/meta/objects/team_member.py index 6f1ca25e2..e7cbf705e 100644 --- a/darwin/future/meta/objects/team_member.py +++ b/darwin/future/meta/objects/team_member.py @@ -1,7 +1,7 @@ from typing import List, Optional from darwin.future.core.client import Client -from darwin.future.data_objects.team import TeamMember, get_team, get_team_members +from darwin.future.data_objects.team import TeamMember, get_team_members from darwin.future.meta.objects.base import MetaBase @@ -11,24 +11,11 @@ class TeamMembersMeta(MetaBase[TeamMember]): def __init__(self, client: Client, members: Optional[List[TeamMember]]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client - self.members = members + super().__init__(members) def __next__(self) -> TeamMember: - if self.members is None: - self.members = get_team_members(self.client) - if self.n < len(self.members): - result = self.members[self.n] - self.n += 1 - return result - else: - raise StopIteration - - def __len__(self) -> int: - if self.members is None: - self.members = get_team_members(self.client) - return len(self.members) - - def __getitem__(self, index: int) -> TeamMember: - if self.members is None: - self.members = get_team_members(self.client) - return self.members[index] \ No newline at end of file + 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 2ef3df3d4..e1946a50d 100644 --- a/darwin/future/meta/queries/dataset.py +++ b/darwin/future/meta/queries/dataset.py @@ -26,21 +26,21 @@ 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/tests/core/test_query.py b/darwin/future/tests/core/test_query.py index abb1a1f92..d9ca6b6bf 100644 --- a/darwin/future/tests/core/test_query.py +++ b/darwin/future/tests/core/test_query.py @@ -59,13 +59,6 @@ def test_query_filter_functionality(base_client: Client, basic_filters: List[Que assert q.filters == basic_filters -def test_query_iterable(base_client: Client, basic_filters: List[Query.QueryFilter], non_abc_query: Type[Query.Query]) -> None: - q = non_abc_query(base_client, 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/meta/objects/test_teammeta.py b/darwin/future/tests/meta/objects/test_teammeta.py index 721634ceb..8045b00db 100644 --- a/darwin/future/tests/meta/objects/test_teammeta.py +++ b/darwin/future/tests/meta/objects/test_teammeta.py @@ -3,18 +3,17 @@ import responses from pytest import fixture, raises -from darwin.future.core.client import DarwinConfig -from darwin.future.meta.client import MetaClient +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 * -def test_team_meta_collects_basic(base_client: Client, base_teams_json: dict) -> None: - query = TeamMeta(base_client) +def test_team_meta_collects_members(base_client: Client, base_team_member: TeamMember, base_team_member_json: dict) -> None: + team = TeamMeta(base_client) with responses.RequestsMock() as rsps: - endpoint = base_client.config.api_endpoint + "teams" - rsps.add(responses.GET, endpoint, json=base_teams_json) - teams = query.collect() - - assert len(teams) == 2 - assert all([isinstance(team, Team) for team in teams]) \ No newline at end of file + endpoint = base_client.config.api_endpoint + "memberships" + rsps.add(responses.GET, endpoint, json=[base_team_member_json]) + members = 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 4cab30d1e..4d5944e3f 100644 --- a/darwin/future/tests/meta/queries/test_dataset.py +++ b/darwin/future/tests/meta/queries/test_dataset.py @@ -13,7 +13,6 @@ def test_dataset_collects_basic(base_client: Client, base_datasets_json: dict) - endpoint = base_client.config.api_endpoint + "datasets" rsps.add(responses.GET, endpoint, json=base_datasets_json) datasets = query.collect() - assert len(datasets) == 2 assert all([isinstance(dataset, Dataset) for dataset in datasets]) From 8ae5d4b4f659964859a585efa3f431ea1b88fc7f Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 26 Jun 2023 19:05:54 +0100 Subject: [PATCH 5/6] client.team update --- darwin/future/meta/client.py | 8 ++++++-- darwin/future/meta/objects/team.py | 4 +++- darwin/future/tests/meta/fixtures.py | 10 ++++++++++ darwin/future/tests/meta/objects/fixtures.py | 10 ++++++++++ darwin/future/tests/meta/objects/test_teammeta.py | 7 ++++--- darwin/future/tests/meta/test_client.py | 14 ++++++++++++++ 6 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 darwin/future/tests/meta/objects/fixtures.py diff --git a/darwin/future/meta/client.py b/darwin/future/meta/client.py index 44024ddc7..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 @@ -29,5 +31,7 @@ def from_api_key(cls, api_key: str, datasets_dir: Optional[Path] = None) -> Meta return cls(config) @property - def team(self) -> str: - return self.config.default_team \ No newline at end of file + def team(self) -> TeamMeta: + if self._team is None: + self._team = TeamMeta(self) + return self._team diff --git a/darwin/future/meta/objects/team.py b/darwin/future/meta/objects/team.py index e6a83f7a6..4af41d78a 100644 --- a/darwin/future/meta/objects/team.py +++ b/darwin/future/meta/objects/team.py @@ -1,7 +1,7 @@ from typing import List, Optional from darwin.future.core.client import Client -from darwin.future.data_objects.team import Team +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 @@ -13,6 +13,8 @@ class TeamMeta(MetaBase[Team]): def __init__(self, client: Client, teams: Optional[List[Team]]=None) -> None: # TODO: Initialise from chaining within MetaClient self.client = client + if not teams: + teams = [get_team(self.client)] super().__init__(teams) 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 index 8045b00db..55ad1dac6 100644 --- a/darwin/future/tests/meta/objects/test_teammeta.py +++ b/darwin/future/tests/meta/objects/test_teammeta.py @@ -7,13 +7,14 @@ 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_client: Client, base_team_member: TeamMember, base_team_member_json: dict) -> None: - team = TeamMeta(base_client) +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 = team.members.collect() + 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/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 From c215e8183db743131233de8ab9891dc1666d9830 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Mon, 26 Jun 2023 19:28:26 +0100 Subject: [PATCH 6/6] remove comment --- darwin/future/data_objects/team.py | 1 - 1 file changed, 1 deletion(-) diff --git a/darwin/future/data_objects/team.py b/darwin/future/data_objects/team.py index a5d748823..1bfaaf0ef 100644 --- a/darwin/future/data_objects/team.py +++ b/darwin/future/data_objects/team.py @@ -4,7 +4,6 @@ from pydantic import validator -# from darwin.future.core.backend import get_team 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