Skip to content

Commit 455e12e

Browse files
authored
Merge pull request #980 from CitrineInformatics/routing-take2
PNE-816 take2
2 parents d0cce4a + cd41e41 commit 455e12e

File tree

5 files changed

+77
-75
lines changed

5 files changed

+77
-75
lines changed

src/citrine/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.11.5"
1+
__version__ = "3.11.6"

src/citrine/_session.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ def checked_request(self, method: str, path: str,
151151
logger.debug('\tmethod: {}'.format(method))
152152
logger.debug('\tpath: {}'.format(path))
153153
logger.debug('\tversion: {}'.format(version))
154+
for k, v in kwargs.items():
155+
logger.debug(f'\t{k}: {v}')
154156

155157
if self._is_access_token_expired():
156158
self._refresh_access_token()

src/citrine/resources/project.py

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Resources that represent both individual and collections of projects."""
2-
from functools import partial
2+
from deprecation import deprecated
33
from typing import Optional, Dict, List, Union, Iterable, Tuple, Iterator
44
from uuid import UUID
5+
from warnings import warn
56

6-
from deprecation import deprecated
77
from gemd.entity.base_entity import BaseEntity
88
from gemd.entity.link_by_uid import LinkByUID
99

@@ -12,7 +12,6 @@
1212
from citrine._serialization import properties
1313
from citrine._session import Session
1414
from citrine._utils.functions import format_escaped_url
15-
from citrine.exceptions import NonRetryableException, ModuleRegistrationFailedException
1615
from citrine.resources.api_error import ApiError
1716
from citrine.resources.branch import BranchCollection
1817
from citrine.resources.dataset import DatasetCollection
@@ -46,7 +45,6 @@
4645
from citrine.resources.project_member import ProjectMember
4746
from citrine.resources.response import Response
4847
from citrine.resources.table_config import TableConfigCollection
49-
from warnings import warn
5048

5149

5250
class Project(Resource['Project']):
@@ -519,14 +517,16 @@ class ProjectCollection(Collection[Project]):
519517
520518
"""
521519

522-
_path_template = '/projects'
520+
@property
521+
def _path_template(self):
522+
if self.team_id is None:
523+
return '/projects'
524+
else:
525+
return '/teams/{team_id}/projects'
523526
_individual_key = 'project'
524527
_collection_key = 'projects'
525528
_resource = Project
526-
527-
@property
528-
def _api_version(self):
529-
return 'v3'
529+
_api_version = 'v3'
530530

531531
def __init__(self, session: Session, *, team_id: Optional[UUID] = None):
532532
self.session = session
@@ -553,6 +553,22 @@ def build(self, data) -> Project:
553553
project.team_id = self.team_id
554554
return project
555555

556+
def get(self, uid: Union[UUID, str]) -> Project:
557+
"""
558+
Get a particular project.
559+
560+
Parameters
561+
----------
562+
uid: UUID or str
563+
The uid of the project to get.
564+
565+
"""
566+
# Only the team-agnostic project get is implemented
567+
if self.team_id is None:
568+
return super().get(uid)
569+
else:
570+
return ProjectCollection(session=self.session).get(uid)
571+
556572
def register(self, name: str, *, description: Optional[str] = None) -> Project:
557573
"""
558574
Create and upload new project.
@@ -564,19 +580,18 @@ def register(self, name: str, *, description: Optional[str] = None) -> Project:
564580
description: str
565581
Long-form description of the project to be created.
566582
583+
Return
584+
-------
585+
Project
586+
The newly registered project.
587+
567588
"""
568589
if self.team_id is None:
569590
raise NotImplementedError("Cannot register a project without a team ID. "
570591
"Use team.projects.register.")
571592

572-
path = format_escaped_url('teams/{team_id}/projects', team_id=self.team_id)
573593
project = Project(name, description=description)
574-
try:
575-
data = self.session.post_resource(path, project.dump(), version=self._api_version)
576-
data = data[self._individual_key]
577-
return self.build(data)
578-
except NonRetryableException as e:
579-
raise ModuleRegistrationFailedException(project.__class__.__name__, e)
594+
return super().register(project)
580595

581596
def list(self, *, per_page: int = 1000) -> Iterator[Project]:
582597
"""
@@ -595,15 +610,7 @@ def list(self, *, per_page: int = 1000) -> Iterator[Project]:
595610
Projects in this collection.
596611
597612
"""
598-
if self.team_id is None:
599-
path = '/projects'
600-
else:
601-
path = format_escaped_url('/teams/{team_id}/projects', team_id=self.team_id)
602-
603-
fetcher = partial(self._fetch_page, path=path)
604-
return self._paginator.paginate(page_fetcher=fetcher,
605-
collection_builder=self._build_collection_elements,
606-
per_page=per_page)
613+
return super().list(per_page=per_page)
607614

608615
def search_all(self, search_params: Optional[Dict]) -> Iterable[Dict]:
609616
"""
@@ -647,12 +654,11 @@ def search_all(self, search_params: Optional[Dict]) -> Iterable[Dict]:
647654
648655
"""
649656
collections = []
650-
path = self._get_path(action="search")
651657
query_params = {'userId': ""}
652658

653659
json = {} if search_params is None else {'search_params': search_params}
654660

655-
data = self.session.post_resource(path,
661+
data = self.session.post_resource(self._get_path(action="search"),
656662
params=query_params,
657663
json=json,
658664
version=self._api_version)
@@ -730,7 +736,11 @@ def delete(self, uid: Union[UUID, str]) -> Response:
730736
If the project is not empty, then the Response will contain a list of all of the project's
731737
resources. These must be deleted before the project can be deleted.
732738
"""
733-
return super().delete(uid)
739+
# Only the team-agnostic project delete is implemented
740+
if self.team_id is None:
741+
return super().delete(uid)
742+
else:
743+
return ProjectCollection(session=self.session).delete(uid)
734744

735745
def update(self, model: Project) -> Project:
736746
"""Projects cannot be updated."""

tests/resources/test_project.py

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -353,36 +353,6 @@ def test_failed_register_no_team(session):
353353
project_collection.register("Project")
354354

355355

356-
def test_project_registration(collection: ProjectCollection, session):
357-
# Given
358-
create_time = parse('2019-09-10T00:00:00+00:00')
359-
project_data = ProjectDataFactory(
360-
name='testing',
361-
description='A sample project',
362-
created_at=int(create_time.timestamp() * 1000) # The lib expects ms since epoch, which is really odd
363-
)
364-
session.set_response({'project': project_data})
365-
366-
# When
367-
with pytest.warns(DeprecationWarning):
368-
created_project = collection.register('testing')
369-
370-
# Then
371-
assert 1 == session.num_calls
372-
expected_call = FakeCall(
373-
method='POST',
374-
path='/projects',
375-
json={
376-
'name': 'testing'
377-
}
378-
)
379-
assert expected_call == session.last_call
380-
381-
assert 'A sample project' == created_project.description
382-
assert 'CREATED' == created_project.status
383-
assert create_time == created_project.created_at
384-
385-
386356
def test_project_registration(collection: ProjectCollection, session):
387357
# Given
388358
create_time = parse('2019-09-10T00:00:00+00:00')
@@ -454,7 +424,7 @@ def test_list_no_team(session):
454424
projects = list(project_collection.list())
455425

456426
assert 1 == session.num_calls
457-
expected_call = FakeCall(method='GET', path=f'/projects', params={'per_page': 1000, 'page': 1})
427+
expected_call = FakeCall(method='GET', path='/projects', params={'per_page': 1000, 'page': 1})
458428
assert expected_call == session.last_call
459429
assert 5 == len(projects)
460430

@@ -472,6 +442,27 @@ def test_list_projects_with_page_params(collection, session):
472442
expected_call = FakeCall(method='GET', path=f'/teams/{collection.team_id}/projects', params={'per_page': 10, 'page': 1})
473443
assert expected_call == session.last_call
474444

445+
def test_search_all_no_team(session):
446+
project_collection = ProjectCollection(session=session)
447+
projects_data = ProjectDataFactory.create_batch(2)
448+
project_name_to_match = projects_data[0]['name']
449+
450+
search_params = {
451+
'name': {
452+
'value': project_name_to_match,
453+
'search_method': 'EXACT'}}
454+
expected_response = [p for p in projects_data if p["name"] == project_name_to_match]
455+
456+
project_collection.session.set_response({'projects': expected_response})
457+
458+
# Then
459+
results = list(project_collection.search_all(search_params=search_params))
460+
461+
expected_call = FakeCall(method='POST', path='/projects/search', params={'userId': ''}, json={'search_params': search_params})
462+
463+
assert 1 == project_collection.session.num_calls
464+
assert expected_call == project_collection.session.last_call
465+
assert 1 == len(results)
475466

476467
def test_search_all(collection: ProjectCollection):
477468
# Given
@@ -490,7 +481,7 @@ def test_search_all(collection: ProjectCollection):
490481
results = list(collection.search_all(search_params=search_params))
491482

492483
expected_call = FakeCall(method='POST',
493-
path='/projects/search',
484+
path=f'/teams/{collection.team_id}/projects/search',
494485
params={'userId': ''},
495486
json={'search_params': {
496487
'name': {
@@ -513,7 +504,7 @@ def test_search_all_no_search_params(collection: ProjectCollection):
513504
result = list(collection.search_all(search_params=None))
514505

515506
expected_call = FakeCall(method='POST',
516-
path='/projects/search',
507+
path=f'/teams/{collection.team_id}/projects/search',
517508
params={'userId': ''},
518509
json={})
519510

@@ -539,7 +530,7 @@ def test_search_projects(collection: ProjectCollection):
539530
result = list(collection.search(search_params=search_params))
540531

541532
expected_call = FakeCall(method='POST',
542-
path='/projects/search',
533+
path=f'/teams/{collection.team_id}/projects/search',
543534
params={'userId': ''},
544535
json={'search_params': {
545536
'name': {
@@ -561,7 +552,7 @@ def test_search_projects_no_search_params(collection: ProjectCollection):
561552
# Then
562553
result = list(collection.search())
563554

564-
expected_call = FakeCall(method='POST', path='/projects/search', params={'userId': ''}, json={})
555+
expected_call = FakeCall(method='POST', path=f'/teams/{collection.team_id}/projects/search', params={'userId': ''}, json={})
565556

566557
assert 1 == collection.session.num_calls
567558
assert expected_call == collection.session.last_call
@@ -577,7 +568,7 @@ def test_delete_project(collection, session):
577568

578569
# Then
579570
assert 1 == session.num_calls
580-
expected_call = FakeCall(method='DELETE', path='/projects/{}'.format(uid))
571+
expected_call = FakeCall(method='DELETE', path=f'/projects/{uid}')
581572
assert expected_call == session.last_call
582573

583574

@@ -607,11 +598,8 @@ def test_list_members(project, session):
607598

608599
# Then
609600
assert 2 == session.num_calls
610-
expect_call_1 = FakeCall(
611-
method='GET',
612-
path='/teams/{}'.format(team_data['id']),
613-
)
614-
expect_call_2 = FakeCall(method='GET', path='/teams/{}/users'.format(project.team_id))
601+
expect_call_1 = FakeCall(method='GET', path=f'/teams/{team_data["id"]}')
602+
expect_call_2 = FakeCall(method='GET', path=f'/teams/{project.team_id}/users')
615603
assert expect_call_1 == session.calls[0]
616604
assert expect_call_2 == session.calls[1]
617605
assert isinstance(members[0], TeamMember)

tests/utils/session.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def __eq__(self, other) -> bool:
3636
if not isinstance(other, FakeCall):
3737
return NotImplemented
3838

39-
return self.method == other.method and \
40-
self.path == other.path and \
41-
self.json == other.json and \
42-
self.params == other.params and \
43-
(not self.version or not other.version or self.version == other.version) # Allows users to check the URL version without forcing everyone to.
39+
return (
40+
self.method == other.method and
41+
self.path.lstrip('/') == other.path.lstrip('/') and # Leading slashes don't affect results
42+
self.json == other.json and
43+
self.params == other.params and
44+
(not self.version or not other.version or self.version == other.version) # Allows users to check the URL version without forcing everyone to.
45+
)
4446

4547

4648
class FakeSession(Session):

0 commit comments

Comments
 (0)