Skip to content

Commit d4ce1df

Browse files
Lenore Kubieanoto-moniz
Lenore Kubie
authored andcommitted
[PNE-241] Data Manager support.
Datasets and all related objects now belong to teams, so new endpoints have been introduced into the platform to support this. As such, all such calls need to be updated. The result is a bunch of `ProjectCollection` methods are being deprecated in favor of their `TeamCollection` equivalents, and many methods which accepted a project or project_id must be moved to a team/team_id. The vast majority of behaviors will remain unchanged when using the deprecated methods, until we bump the major version of the SDK and drop the old endpoints. The major exception is that creating a dataset on a project endpoint will register it to a team, so it will be omitted when you list your datasets by project.
1 parent a81506f commit d4ce1df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1436
-447
lines changed

src/citrine/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.3.0"
1+
__version__ = "3.4.0"

src/citrine/_utils/functions.py

+23
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,26 @@ def resource_path(*,
319319
new_url = base._replace(path='/'.join(path), query=query).geturl()
320320

321321
return format_escaped_url(new_url, *action, **kwargs, uid=uid)
322+
323+
324+
def _data_manager_deprecation_checks(session, project_id: UUID, team_id: UUID, obj_type: str):
325+
if project_id is None and team_id is None:
326+
raise TypeError("Missing one required argument: team_id.")
327+
elif project_id is not None:
328+
warn(
329+
f"{obj_type} now belong to Teams. Access through projects has been deprecated since "
330+
"v3.4.0, and will be removed in v4.0. Please go through your Team or Dataset.",
331+
DeprecationWarning)
332+
if team_id is None:
333+
# avoiding a circular import
334+
from citrine.resources.project import Project
335+
team_id = Project.get_team_id_from_project_id(session=session, project_id=project_id)
336+
return team_id
337+
338+
339+
def _pad_positional_args(args, n):
340+
if len(args) > 0:
341+
warn("Positional arguments are deprecated and will be removed in v4.0. Please use keyword "
342+
"arguments instead.",
343+
DeprecationWarning)
344+
return args + (None, ) * (n - len(args))

src/citrine/jobs/job.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from logging import getLogger
22
from time import time, sleep
3-
from typing import Union
3+
from typing import Union, Optional
44
from uuid import UUID
55

66
from citrine._serialization.properties import Set as PropertySet, String, Object
@@ -59,9 +59,10 @@ class JobStatusResponse(Resource['JobStatusResponse']):
5959

6060

6161
def _poll_for_job_completion(session: Session,
62-
project_id: Union[UUID, str],
6362
job: Union[JobSubmissionResponse, UUID, str],
6463
*,
64+
team_id: Optional[Union[UUID, str]] = None,
65+
project_id: Optional[Union[UUID, str]] = None,
6566
timeout: float = 2 * 60,
6667
polling_delay: float = 2.0,
6768
raise_errors: bool = True,
@@ -96,7 +97,12 @@ def _poll_for_job_completion(session: Session,
9697
job_id = job.job_id
9798
else:
9899
job_id = job # pragma: no cover
99-
path = format_escaped_url('projects/{}/execution/job-status', project_id)
100+
if project_id is None and team_id is None:
101+
raise TypeError('Either a project_id or team_id must be provided to poll a job.')
102+
if team_id is not None:
103+
path = format_escaped_url('teams/{}/execution/job-status', team_id)
104+
else:
105+
path = format_escaped_url('projects/{}/execution/job-status', project_id)
100106
params = {'job_id': job_id}
101107
start_time = time()
102108
while True:

src/citrine/resources/condition_template.py

-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ def __str__(self):
5858
class ConditionTemplateCollection(AttributeTemplateCollection[ConditionTemplate]):
5959
"""A collection of condition templates."""
6060

61-
_path_template = 'projects/{project_id}/datasets/{dataset_id}/condition-templates'
62-
_dataset_agnostic_path_template = 'projects/{project_id}/condition-templates'
6361
_individual_key = 'condition_template'
6462
_collection_key = 'condition_templates'
6563
_resource = ConditionTemplate

src/citrine/resources/data_concepts.py

+50-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Top-level class for all data concepts objects and collections thereof."""
2-
from abc import abstractmethod, ABC
32
import re
3+
from abc import abstractmethod, ABC
44
from typing import TypeVar, Type, List, Union, Optional, Iterator, Iterable
55
from uuid import UUID, uuid4
66

@@ -18,8 +18,8 @@
1818
from citrine._serialization.properties import UUID as PropertyUUID
1919
from citrine._serialization.serializable import Serializable
2020
from citrine._session import Session
21-
from citrine._utils.functions import scrub_none, replace_objects_with_links, \
22-
format_escaped_url
21+
from citrine._utils.functions import _data_manager_deprecation_checks, format_escaped_url, \
22+
_pad_positional_args, replace_objects_with_links, scrub_none
2323
from citrine.exceptions import BadRequest
2424
from citrine.resources.audit_info import AuditInfo
2525
from citrine.jobs.job import _poll_for_job_completion
@@ -202,28 +202,56 @@ class DataConceptsCollection(Collection[ResourceType], ABC):
202202
203203
Parameters
204204
----------
205-
project_id: UUID
206-
The uid of the project that this collection belongs to.
205+
team_id: UUID
206+
The uid of the team that this collection belongs to.
207207
dataset_id: UUID
208208
The uid of the dataset that this collection belongs to. If None then the collection
209-
ranges over all datasets in the project. Note that this is only allowed for certain
209+
ranges over all datasets in the team. Note that this is only allowed for certain
210210
actions. For example, you can use :func:`list_by_tag` to search over all datasets,
211211
but when using :func:`register` to upload or update an object, a dataset must be specified.
212212
session: Session
213213
The Citrine session used to connect to the database.
214214
215215
"""
216216

217-
def __init__(self, project_id: UUID, dataset_id: UUID, session: Session):
218-
self.project_id = project_id
219-
self.dataset_id = dataset_id
220-
self.session = session
217+
def __init__(self,
218+
*args,
219+
session: Session = None,
220+
dataset_id: Optional[UUID] = None,
221+
team_id: Optional[UUID] = None,
222+
project_id: Optional[UUID] = None):
223+
# Handle positional arguments for backward compatibility
224+
args = _pad_positional_args(args, 3)
225+
self.project_id = project_id or args[0]
226+
self.dataset_id = dataset_id or args[1]
227+
self.session = session or args[2]
228+
if self.session is None:
229+
raise TypeError("Missing one required argument: session.")
230+
231+
self.team_id = _data_manager_deprecation_checks(
232+
session=self.session,
233+
project_id=self.project_id,
234+
team_id=team_id,
235+
obj_type="GEMD Objects")
221236

222237
@classmethod
223238
@abstractmethod
224239
def get_type(cls) -> Type[Serializable]:
225240
"""Return the resource type in the collection."""
226241

242+
@property
243+
def _path_template(self):
244+
collection_key = self._collection_key.replace("_", "-")
245+
return f'teams/{self.team_id}/datasets/{self.dataset_id}/{collection_key}'
246+
247+
# After Data Manager deprecation, both can use the `teams/...` path.
248+
@property
249+
def _dataset_agnostic_path_template(self):
250+
if self.project_id is None:
251+
return f'teams/{self.team_id}/{self._collection_key.replace("_","-")}'
252+
else:
253+
return f'projects/{self.project_id}/{self._collection_key.replace("_","-")}'
254+
227255
def build(self, data: dict) -> ResourceType:
228256
"""
229257
Build an object of type ResourceType from a serialized dictionary.
@@ -390,7 +418,9 @@ def register_all(self,
390418
391419
"""
392420
from citrine.resources.gemd_resource import GEMDResourceCollection
393-
gemd_collection = GEMDResourceCollection(self.project_id, self.dataset_id, self.session)
421+
gemd_collection = GEMDResourceCollection(team_id=self.team_id,
422+
dataset_id=self.dataset_id,
423+
session=self.session)
394424
return gemd_collection.register_all(
395425
models,
396426
dry_run=dry_run,
@@ -482,7 +512,7 @@ def async_update(self, model: ResourceType, *,
482512
job_id = response_json["job_id"]
483513

484514
if wait_for_response:
485-
self.poll_async_update_job(job_id, timeout=timeout,
515+
self.poll_async_update_job(job_id=job_id, timeout=timeout,
486516
polling_delay=polling_delay)
487517

488518
# That worked, return nothing or return the object
@@ -525,8 +555,12 @@ def poll_async_update_job(self, job_id: UUID, *, timeout: float = 2 * 60,
525555
526556
"""
527557
# Poll for job completion - this will raise an error if the job failed
528-
_poll_for_job_completion(self.session, self.project_id, job_id, timeout=timeout,
529-
polling_delay=polling_delay)
558+
_poll_for_job_completion(
559+
session=self.session,
560+
team_id=self.team_id,
561+
project_id=self.project_id,
562+
job=job_id, timeout=timeout,
563+
polling_delay=polling_delay)
530564

531565
# That worked, nothing returned in this case
532566
return None
@@ -675,8 +709,8 @@ def _get_relation(self, relation: str, uid: Union[UUID, str, LinkByUID, BaseEnti
675709
link = _make_link_by_uid(uid)
676710
raw_objects = self.session.cursor_paged_resource(
677711
self.session.get_resource,
678-
format_escaped_url('projects/{}/{}/{}/{}/{}',
679-
self.project_id,
712+
format_escaped_url('teams/{}/{}/{}/{}/{}',
713+
self.team_id,
680714
relation,
681715
link.scope,
682716
link.id,

0 commit comments

Comments
 (0)