kalbio 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kalbio/__init__.py +1 -0
- kalbio/_kaleidoscope_model.py +111 -0
- kalbio/activities.py +1202 -0
- kalbio/client.py +463 -0
- kalbio/dashboards.py +276 -0
- kalbio/entity_fields.py +474 -0
- kalbio/entity_types.py +188 -0
- kalbio/exports.py +126 -0
- kalbio/helpers.py +52 -0
- kalbio/imports.py +89 -0
- kalbio/labels.py +88 -0
- kalbio/programs.py +96 -0
- kalbio/property_fields.py +81 -0
- kalbio/record_views.py +191 -0
- kalbio/records.py +1173 -0
- kalbio/workspace.py +315 -0
- kalbio-0.2.0.dist-info/METADATA +289 -0
- kalbio-0.2.0.dist-info/RECORD +19 -0
- kalbio-0.2.0.dist-info/WHEEL +4 -0
kalbio/activities.py
ADDED
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
"""Activities Module for Kaleidoscope API Client.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive functionality for managing activities (tasks, experiments,
|
|
4
|
+
projects, stages, milestones, and design cycles) within the Kaleidoscope platform. It includes
|
|
5
|
+
models for activities, activity definitions, and properties, as well as service classes for
|
|
6
|
+
performing CRUD operations and managing activity workflows.
|
|
7
|
+
|
|
8
|
+
The module manages:
|
|
9
|
+
|
|
10
|
+
- Activity creation, updates, and status transitions
|
|
11
|
+
- Activity definitions
|
|
12
|
+
- Properties
|
|
13
|
+
- Records of activities
|
|
14
|
+
- User and group assignments
|
|
15
|
+
- Labels of activities
|
|
16
|
+
- Related programs
|
|
17
|
+
- Parent-child activity relationships
|
|
18
|
+
- Activity dependencies and scheduling
|
|
19
|
+
|
|
20
|
+
Classes and types:
|
|
21
|
+
ActivityStatusEnum: Enumeration of possible activity statuses used across activity workflows.
|
|
22
|
+
ActivityType: Type alias for supported activity categories (task, experiment, project, stage, milestone, cycle).
|
|
23
|
+
Property: Model representing a property (field) attached to entities, with update and file upload helpers.
|
|
24
|
+
ActivityDefinition: Template/definition for activities (templates for programs, users, groups, labels, and properties).
|
|
25
|
+
Activity: Core activity model (task/experiment/project) with cached relations, record accessors, and update helpers.
|
|
26
|
+
ActivitiesService: Service class exposing CRUD and retrieval operations for activities and activity definitions.
|
|
27
|
+
ActivityIdentifier: Identifier union for activities (instance, title, or UUID).
|
|
28
|
+
DefinitionIdentifier: Identifier union for activity definitions (instance, title, or UUID).
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
# Create a new activity
|
|
33
|
+
activity = client.activities.create_activity(
|
|
34
|
+
title="Synthesis Experiment",
|
|
35
|
+
activity_type="experiment",
|
|
36
|
+
program_ids=["program-uuid", ...],
|
|
37
|
+
assigned_user_ids=["user-uuid", ...]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Update activity status
|
|
41
|
+
activity.update(status=ActivityStatusEnum.IN_PROGRESS)
|
|
42
|
+
|
|
43
|
+
# Add records to activity
|
|
44
|
+
activity.add_records(["record-uuid"])
|
|
45
|
+
|
|
46
|
+
# Get activity data
|
|
47
|
+
record_data = activity.get_record_data()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
This module uses Pydantic for data validation and serialization. All datetime
|
|
52
|
+
objects are timezone-aware and follow ISO 8601 format.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
import logging
|
|
57
|
+
from datetime import datetime
|
|
58
|
+
from enum import Enum
|
|
59
|
+
from functools import cached_property, lru_cache
|
|
60
|
+
|
|
61
|
+
import cachetools.func
|
|
62
|
+
from kalbio._kaleidoscope_model import _KaleidoscopeBaseModel
|
|
63
|
+
from kalbio.client import KaleidoscopeClient
|
|
64
|
+
from kalbio.entity_fields import DataFieldTypeEnum
|
|
65
|
+
from kalbio.programs import Program
|
|
66
|
+
from kalbio.labels import Label
|
|
67
|
+
from kalbio.workspace import WorkspaceUser, WorkspaceGroup
|
|
68
|
+
from typing import Any, BinaryIO, List, Literal, Optional, Union
|
|
69
|
+
from typing import TYPE_CHECKING
|
|
70
|
+
|
|
71
|
+
if TYPE_CHECKING:
|
|
72
|
+
from kalbio.records import Record, RecordIdentifier
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_logger = logging.getLogger(__name__)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ActivityStatusEnum(str, Enum):
|
|
79
|
+
"""Enumeration of possible activity status values.
|
|
80
|
+
|
|
81
|
+
This enum defines all possible states that an activity can be in during its lifecycle,
|
|
82
|
+
including general workflow states, review states, and domain-specific states for
|
|
83
|
+
design, synthesis, testing, and compound selection processes.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
REQUESTED (str): Activity has been requested but not yet started.
|
|
87
|
+
TODO (str): Activity is queued to be worked on.
|
|
88
|
+
IN_PROGRESS (str): Activity is currently being worked on.
|
|
89
|
+
NEEDS_REVIEW (str): Activity requires review.
|
|
90
|
+
BLOCKED (str): Activity is blocked by dependencies or issues.
|
|
91
|
+
PAUSED (str): Activity has been temporarily paused.
|
|
92
|
+
CANCELLED (str): Activity has been cancelled.
|
|
93
|
+
IN_REVIEW (str): Activity is currently under review.
|
|
94
|
+
LOCKED (str): Activity is locked from modifications.
|
|
95
|
+
TO_REVIEW (str): Activity is ready to be reviewed.
|
|
96
|
+
UPLOAD_COMPLETE (str): Upload process for the activity is complete.
|
|
97
|
+
NEW (str): Newly created activity.
|
|
98
|
+
IN_DESIGN (str): Activity is in the design phase.
|
|
99
|
+
READY_FOR_MAKE (str): Activity is ready for manufacturing/creation.
|
|
100
|
+
IN_SYNTHESIS (str): Activity is in the synthesis phase.
|
|
101
|
+
IN_TEST (str): Activity is in the testing phase.
|
|
102
|
+
IN_ANALYSIS (str): Activity is in the analysis phase.
|
|
103
|
+
PARKED (str): Activity has been parked for later consideration.
|
|
104
|
+
COMPLETE (str): Activity has been completed.
|
|
105
|
+
IDEATION (str): Activity is in the ideation phase.
|
|
106
|
+
TWO_D_SELECTION (str): Activity is in 2D selection phase.
|
|
107
|
+
COMPUTATION (str): Activity is in the computation phase.
|
|
108
|
+
COMPOUND_SELECTION (str): Activity is in the compound selection phase.
|
|
109
|
+
SELECTED (str): Activity or compound has been selected.
|
|
110
|
+
QUEUE_FOR_SYNTHESIS (str): Activity is queued for synthesis.
|
|
111
|
+
DATA_REVIEW (str): Activity is in the data review phase.
|
|
112
|
+
DONE (str): Activity is done.
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
```python
|
|
116
|
+
from kalbio.activities import ActivityStatusEnum
|
|
117
|
+
|
|
118
|
+
status = ActivityStatusEnum.IN_PROGRESS
|
|
119
|
+
print(status.value)
|
|
120
|
+
```
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
REQUESTED = "requested"
|
|
124
|
+
TODO = "to do"
|
|
125
|
+
IN_PROGRESS = "in progress"
|
|
126
|
+
NEEDS_REVIEW = "needs review"
|
|
127
|
+
BLOCKED = "blocked"
|
|
128
|
+
PAUSED = "paused"
|
|
129
|
+
CANCELLED = "cancelled"
|
|
130
|
+
IN_REVIEW = "in review"
|
|
131
|
+
LOCKED = "locked"
|
|
132
|
+
|
|
133
|
+
TO_REVIEW = "to review"
|
|
134
|
+
UPLOAD_COMPLETE = "upload complete"
|
|
135
|
+
|
|
136
|
+
NEW = "new"
|
|
137
|
+
IN_DESIGN = "in design"
|
|
138
|
+
READY_FOR_MAKE = "ready for make"
|
|
139
|
+
IN_SYNTHESIS = "in synthesis"
|
|
140
|
+
IN_TEST = "in test"
|
|
141
|
+
IN_ANALYSIS = "in analysis"
|
|
142
|
+
PARKED = "parked"
|
|
143
|
+
COMPLETE = "complete"
|
|
144
|
+
|
|
145
|
+
IDEATION = "ideation"
|
|
146
|
+
TWO_D_SELECTION = "2D selection"
|
|
147
|
+
COMPUTATION = "computation"
|
|
148
|
+
COMPOUND_SELECTION = "compound selection"
|
|
149
|
+
SELECTED = "selected"
|
|
150
|
+
QUEUE_FOR_SYNTHESIS = "queue for synthesis"
|
|
151
|
+
DATA_REVIEW = "data review"
|
|
152
|
+
|
|
153
|
+
DONE = "done"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
type ActivityType = Union[
|
|
157
|
+
Literal["task"],
|
|
158
|
+
Literal["experiment"],
|
|
159
|
+
Literal["project"],
|
|
160
|
+
Literal["stage"],
|
|
161
|
+
Literal["milestone"],
|
|
162
|
+
Literal["cycle"],
|
|
163
|
+
]
|
|
164
|
+
"""Type alias representing the valid types of activities in the system.
|
|
165
|
+
|
|
166
|
+
This type defines the allowed string values for the `activity_type` field
|
|
167
|
+
in Activity and ActivityDefinition models.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
ACTIVITY_TYPE_TO_LABEL: dict[ActivityType, str] = {
|
|
171
|
+
"task": "Task",
|
|
172
|
+
"experiment": "Experiment",
|
|
173
|
+
"project": "Project",
|
|
174
|
+
"stage": "Stage",
|
|
175
|
+
"milestone": "Milestone",
|
|
176
|
+
"cycle": "Design cycle",
|
|
177
|
+
}
|
|
178
|
+
"""Dictionary mapping activity type keys to their human-readable labels.
|
|
179
|
+
|
|
180
|
+
This mapping is used to convert the internal `activity_type` identifiers
|
|
181
|
+
into display-friendly strings for UI and reporting purposes.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Property(_KaleidoscopeBaseModel):
|
|
186
|
+
"""Represents a property in the Kaleidoscope system.
|
|
187
|
+
|
|
188
|
+
A Property is a data field associated with an entity that contains a value of a specific type.
|
|
189
|
+
It includes metadata about when and by whom it was created/updated, and provides methods
|
|
190
|
+
to update its content.
|
|
191
|
+
|
|
192
|
+
Attributes:
|
|
193
|
+
id (str): UUID of the property.
|
|
194
|
+
property_field_id (str): UUID to the property field that defines this
|
|
195
|
+
property's schema.
|
|
196
|
+
content (Any): The actual value/content stored in this property.
|
|
197
|
+
created_at (datetime): Timestamp when the property was created.
|
|
198
|
+
last_updated_by (str): UUID of the user who last updated this property.
|
|
199
|
+
created_by (str): UUID of the user who created this property.
|
|
200
|
+
property_name (str): Human-readable name of the property.
|
|
201
|
+
field_type (DataFieldTypeEnum): The data type of this property's content.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
```python
|
|
205
|
+
from kalbio.activities import Property
|
|
206
|
+
|
|
207
|
+
prop = Property(
|
|
208
|
+
id="prop_uuid",
|
|
209
|
+
property_field_id="field_uuid",
|
|
210
|
+
content="In progress",
|
|
211
|
+
created_at=datetime.utcnow(),
|
|
212
|
+
last_updated_by="user_uuid",
|
|
213
|
+
created_by="user_uuid",
|
|
214
|
+
property_name="Status",
|
|
215
|
+
field_type=DataFieldTypeEnum.TEXT,
|
|
216
|
+
)
|
|
217
|
+
print(prop.property_name, prop.content)
|
|
218
|
+
```
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
property_field_id: str
|
|
222
|
+
content: Any
|
|
223
|
+
created_at: datetime
|
|
224
|
+
last_updated_by: str
|
|
225
|
+
created_by: str
|
|
226
|
+
property_name: str
|
|
227
|
+
field_type: DataFieldTypeEnum
|
|
228
|
+
|
|
229
|
+
def __str__(self):
|
|
230
|
+
return f"Property({self.property_name}:{self.content})"
|
|
231
|
+
|
|
232
|
+
def update_property(self, property_value: Any) -> None:
|
|
233
|
+
"""Update the property with a new value.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
property_value: The new value to set for the property.
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
```python
|
|
240
|
+
prop.update_property("Reviewed")
|
|
241
|
+
```
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
resp = self._client._put(
|
|
245
|
+
"/properties/" + self.id, {"content": property_value}
|
|
246
|
+
)
|
|
247
|
+
if resp:
|
|
248
|
+
for key, value in resp.items():
|
|
249
|
+
if hasattr(self, key):
|
|
250
|
+
setattr(self, key, value)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
_logger.error(f"Error updating property {self.id}: {e}")
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
def update_property_file(
|
|
256
|
+
self,
|
|
257
|
+
file_name: str,
|
|
258
|
+
file_data: BinaryIO,
|
|
259
|
+
file_type: str,
|
|
260
|
+
) -> dict | None:
|
|
261
|
+
"""Update a property by uploading a file.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
file_name: The name of the file to be updated.
|
|
265
|
+
file_data: The binary data of the file to be updated.
|
|
266
|
+
file_type: The MIME type of the file to be updated.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
A dict of response JSON data (contains reference to the
|
|
270
|
+
uploaded file) if request successful, otherwise None.
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
```python
|
|
274
|
+
with open("report.pdf", "rb") as file_data:
|
|
275
|
+
upload_info = prop.update_property_file(
|
|
276
|
+
file_name="report.pdf",
|
|
277
|
+
file_data=file_data,
|
|
278
|
+
file_type="application/pdf",
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
resp = self._client._post_file(
|
|
284
|
+
"/properties/" + self.id + "/file",
|
|
285
|
+
(file_name, file_data, file_type),
|
|
286
|
+
)
|
|
287
|
+
if resp is None or len(resp) == 0:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
return resp
|
|
291
|
+
except Exception as e:
|
|
292
|
+
_logger.error(f"Error adding file to property {self.id}: {e}")
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class ActivityDefinition(_KaleidoscopeBaseModel):
|
|
297
|
+
"""Represents the definition of an activity in the Kaleidoscope system.
|
|
298
|
+
|
|
299
|
+
An ActivityDefinition contains a template for the metadata about a task or activity,
|
|
300
|
+
including associated programs, users, groups, labels, and properties.
|
|
301
|
+
|
|
302
|
+
Attributes:
|
|
303
|
+
id (str): UUID of the Activity Definition.
|
|
304
|
+
program_ids (List[str]): List of program UUIDs associated with this activity.
|
|
305
|
+
title (str): The title of the activity.
|
|
306
|
+
activity_type (ActivityType): The type/category of the activity.
|
|
307
|
+
status (Optional[ActivityStatusEnum]): The current status of the activity.
|
|
308
|
+
Defaults to None if not specified.
|
|
309
|
+
assigned_user_ids (List[str]): List of user IDs assigned to this activity.
|
|
310
|
+
assigned_group_ids (List[str]): List of group IDs assigned to this activity.
|
|
311
|
+
label_ids (List[str]): List of label identifiers associated with this activity.
|
|
312
|
+
properties (List[Property]): List of properties that define additional
|
|
313
|
+
characteristics of the activity.
|
|
314
|
+
external_id (Optional[str]): The id of the activity definition if it was imported from an external source
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
```python
|
|
318
|
+
definition = client.activities.get_definition_by_id("definition_uuid")
|
|
319
|
+
if definition:
|
|
320
|
+
print(definition.title, definition.activity_type)
|
|
321
|
+
```
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
program_ids: List[str]
|
|
325
|
+
title: str
|
|
326
|
+
activity_type: ActivityType
|
|
327
|
+
status: Optional[ActivityStatusEnum] = None
|
|
328
|
+
assigned_user_ids: List[str]
|
|
329
|
+
assigned_group_ids: List[str]
|
|
330
|
+
label_ids: List[str]
|
|
331
|
+
properties: List[Property]
|
|
332
|
+
external_id: Optional[str] = None
|
|
333
|
+
|
|
334
|
+
def __str__(self):
|
|
335
|
+
return f"{self.id}:{self.title}"
|
|
336
|
+
|
|
337
|
+
@cached_property
|
|
338
|
+
def activities(self) -> List[Activity]:
|
|
339
|
+
"""Get the activities for this activity definition.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
The activities associated with this
|
|
343
|
+
activity definition.
|
|
344
|
+
|
|
345
|
+
Note:
|
|
346
|
+
This is a cached property.
|
|
347
|
+
|
|
348
|
+
Example:
|
|
349
|
+
```python
|
|
350
|
+
definition = client.activities.get_definition_by_id("definition_uuid")
|
|
351
|
+
related = definition.activities if definition else []
|
|
352
|
+
```
|
|
353
|
+
"""
|
|
354
|
+
return [
|
|
355
|
+
a
|
|
356
|
+
for a in self._client.activities.get_activities()
|
|
357
|
+
if a.definition_id == self.id
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class Activity(_KaleidoscopeBaseModel):
|
|
362
|
+
"""Represents an activity (e.g. task or experiment) within the Kaleidoscope system.
|
|
363
|
+
|
|
364
|
+
An Activity is a unit of work that can be assigned to users or groups, have dependencies,
|
|
365
|
+
and contain associated records and properties. Activities can be organized hierarchically
|
|
366
|
+
with parent-child relationships and linked to programs.
|
|
367
|
+
|
|
368
|
+
Attributes:
|
|
369
|
+
id (str): Unique identifier for the model instance.
|
|
370
|
+
created_at (datetime): The timestamp when the activity was created.
|
|
371
|
+
parent_id (Optional[str]): The ID of the parent activity, if this is a child activity.
|
|
372
|
+
child_ids (List[str]): List of child activity IDs.
|
|
373
|
+
definition_id (Optional[str]): The ID of the activity definition template.
|
|
374
|
+
program_ids (List[str]): List of program IDs this activity belongs to.
|
|
375
|
+
activity_type (ActivityType): The type/category of the activity.
|
|
376
|
+
title (str): The title of the activity.
|
|
377
|
+
description (Any): Detailed description of the activity.
|
|
378
|
+
status (ActivityStatusEnum): Current status of the activity.
|
|
379
|
+
assigned_user_ids (List[str]): List of user IDs assigned to this activity.
|
|
380
|
+
assigned_group_ids (List[str]): List of group IDs assigned to this activity.
|
|
381
|
+
due_date (Optional[datetime]): The deadline for completing the activity.
|
|
382
|
+
start_date (Optional[datetime]): The scheduled start date for the activity.
|
|
383
|
+
duration (Optional[int]): Expected duration of the activity.
|
|
384
|
+
completed_at_date (Optional[datetime]): The timestamp when the activity was completed.
|
|
385
|
+
dependencies (List[str]): List of activity IDs that this activity depends on.
|
|
386
|
+
label_ids (List[str]): List of label IDs associated with this activity.
|
|
387
|
+
is_draft (bool): Whether the activity is in draft status.
|
|
388
|
+
properties (List[Property]): List of custom properties associated with the activity.
|
|
389
|
+
external_id (Optional[str]): The id of the activity if it was imported from an external source
|
|
390
|
+
all_record_ids (List[str]): All record IDs associated with the activity across operations.
|
|
391
|
+
|
|
392
|
+
Example:
|
|
393
|
+
```python
|
|
394
|
+
activity = client.activities.get_activity_by_id("activity_uuid")
|
|
395
|
+
if activity:
|
|
396
|
+
print(activity.title, activity.status)
|
|
397
|
+
first_record = activity.records[0] if activity.records else None
|
|
398
|
+
```
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
created_at: datetime
|
|
402
|
+
parent_id: Optional[str] = None
|
|
403
|
+
child_ids: List[str]
|
|
404
|
+
definition_id: Optional[str] = None
|
|
405
|
+
program_ids: List[str]
|
|
406
|
+
activity_type: ActivityType
|
|
407
|
+
title: str
|
|
408
|
+
description: Any
|
|
409
|
+
status: ActivityStatusEnum
|
|
410
|
+
assigned_user_ids: List[str]
|
|
411
|
+
assigned_group_ids: List[str]
|
|
412
|
+
due_date: Optional[datetime] = None
|
|
413
|
+
start_date: Optional[datetime] = None
|
|
414
|
+
duration: Optional[int] = None
|
|
415
|
+
completed_at_date: Optional[datetime] = None
|
|
416
|
+
dependencies: List[str]
|
|
417
|
+
label_ids: List[str]
|
|
418
|
+
is_draft: bool
|
|
419
|
+
properties: List[Property]
|
|
420
|
+
external_id: Optional[str] = None
|
|
421
|
+
|
|
422
|
+
# operation fields
|
|
423
|
+
all_record_ids: List[str]
|
|
424
|
+
|
|
425
|
+
def __str__(self):
|
|
426
|
+
return f'Activity("{self.title}")'
|
|
427
|
+
|
|
428
|
+
@cached_property
|
|
429
|
+
def activity_definition(self) -> ActivityDefinition | None:
|
|
430
|
+
"""Get the activity definition for this activity.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
The activity definition associated with this
|
|
434
|
+
activity. If the activity has no definition, returns None.
|
|
435
|
+
|
|
436
|
+
Note:
|
|
437
|
+
This is a cached property.
|
|
438
|
+
|
|
439
|
+
Example:
|
|
440
|
+
```python
|
|
441
|
+
definition = activity.activity_definition
|
|
442
|
+
print(definition.title if definition else "No template")
|
|
443
|
+
```
|
|
444
|
+
"""
|
|
445
|
+
if self.definition_id:
|
|
446
|
+
return self._client.activities.get_definition_by_id(self.definition_id)
|
|
447
|
+
else:
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
@cached_property
|
|
451
|
+
def assigned_users(self) -> List[WorkspaceUser]:
|
|
452
|
+
"""Get the assigned users for this activity.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
The users assigned to this activity.
|
|
456
|
+
|
|
457
|
+
Note:
|
|
458
|
+
This is a cached property.
|
|
459
|
+
"""
|
|
460
|
+
return self._client.workspace.get_members_by_ids(self.assigned_user_ids)
|
|
461
|
+
|
|
462
|
+
@cached_property
|
|
463
|
+
def assigned_groups(self) -> List[WorkspaceGroup]:
|
|
464
|
+
"""Get the assigned groups for this activity.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
The groups assigned to this activity.
|
|
468
|
+
|
|
469
|
+
Note:
|
|
470
|
+
This is a cached property.
|
|
471
|
+
"""
|
|
472
|
+
return self._client.workspace.get_groups_by_ids(self.assigned_group_ids)
|
|
473
|
+
|
|
474
|
+
@cached_property
|
|
475
|
+
def labels(self) -> List[Label]:
|
|
476
|
+
"""Get the labels for this activity.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
The labels associated with this activity.
|
|
480
|
+
|
|
481
|
+
Note:
|
|
482
|
+
This is a cached property.
|
|
483
|
+
|
|
484
|
+
Example:
|
|
485
|
+
```python
|
|
486
|
+
label_names = [label.name for label in activity.labels]
|
|
487
|
+
```
|
|
488
|
+
"""
|
|
489
|
+
return self._client.labels.get_labels_by_ids(self.label_ids)
|
|
490
|
+
|
|
491
|
+
@cached_property
|
|
492
|
+
def programs(self) -> List[Program]:
|
|
493
|
+
"""Retrieve the programs associated with this activity.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
A list of Program instances fetched by their IDs.
|
|
497
|
+
|
|
498
|
+
Note:
|
|
499
|
+
This is a cached property.
|
|
500
|
+
|
|
501
|
+
Example:
|
|
502
|
+
```python
|
|
503
|
+
program_titles = [program.title for program in activity.programs]
|
|
504
|
+
```
|
|
505
|
+
"""
|
|
506
|
+
return self._client.programs.get_programs_by_ids(self.program_ids)
|
|
507
|
+
|
|
508
|
+
@cached_property
|
|
509
|
+
def child_activities(self) -> List[Activity]:
|
|
510
|
+
"""Retrieve the child activities associated with this activity.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
A list of Activity objects representing the child activities.
|
|
514
|
+
|
|
515
|
+
Note:
|
|
516
|
+
This is a cached property.
|
|
517
|
+
"""
|
|
518
|
+
try:
|
|
519
|
+
resp = self._client._get("/activities/" + self.id + "/activities")
|
|
520
|
+
return self._client.activities._create_activity_list(resp)
|
|
521
|
+
except Exception as e:
|
|
522
|
+
_logger.error(f"Error fetching child activities: {e}")
|
|
523
|
+
return []
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def records(self) -> List["Record"]:
|
|
527
|
+
"""Retrieve the records associated with this activity.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
A list of Record objects corresponding to the activity.
|
|
531
|
+
|
|
532
|
+
Note:
|
|
533
|
+
This is a cached property.
|
|
534
|
+
"""
|
|
535
|
+
try:
|
|
536
|
+
resp = self._client._get("/operations/" + self.id + "/records")
|
|
537
|
+
return [
|
|
538
|
+
rec for rec in self._client.records._create_record_list(resp) if rec
|
|
539
|
+
]
|
|
540
|
+
except Exception as e:
|
|
541
|
+
_logger.error(f"Error fetching records: {e}")
|
|
542
|
+
return []
|
|
543
|
+
|
|
544
|
+
def get_record(self, identifier: RecordIdentifier) -> Record | None:
|
|
545
|
+
"""Retrieves the record with the given identifier if it is in the operation.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
identifier: An identifier for a Record.
|
|
549
|
+
|
|
550
|
+
This method will accept and resolve any type of RecordIdentifier.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
The record if it is in the operation, otherwise None
|
|
554
|
+
|
|
555
|
+
Example:
|
|
556
|
+
```python
|
|
557
|
+
record = activity.get_record("record_uuid")
|
|
558
|
+
```
|
|
559
|
+
"""
|
|
560
|
+
idx = self._client.records._resolve_to_record_id(identifier)
|
|
561
|
+
|
|
562
|
+
if idx is None:
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
return next(
|
|
566
|
+
(r for r in self.records if r.id == idx),
|
|
567
|
+
None,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
def has_record(self, identifier: RecordIdentifier) -> bool:
|
|
571
|
+
"""Retrieve whether a record with the given identifier is in the operation
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
identifier: An identifier for a Record.
|
|
575
|
+
|
|
576
|
+
This method will accept and resolve any type of RecordIdentifier.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Whether the record is in the operation
|
|
580
|
+
|
|
581
|
+
Example:
|
|
582
|
+
```python
|
|
583
|
+
has_link = activity.has_record("record_uuid")
|
|
584
|
+
```
|
|
585
|
+
"""
|
|
586
|
+
return self.get_record(identifier) is not None
|
|
587
|
+
|
|
588
|
+
def update(self, **kwargs: Any) -> None:
|
|
589
|
+
"""Update the activity with the provided keyword arguments.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
**kwargs: Arbitrary keyword arguments representing fields to update
|
|
593
|
+
for the activity.
|
|
594
|
+
|
|
595
|
+
Note:
|
|
596
|
+
After calling update(), cached properties may be stale. Re-fetch the activity if needed.
|
|
597
|
+
|
|
598
|
+
Example:
|
|
599
|
+
```python
|
|
600
|
+
activity.update(status=ActivityStatusEnum.IN_PROGRESS)
|
|
601
|
+
```
|
|
602
|
+
"""
|
|
603
|
+
try:
|
|
604
|
+
resp = self._client._put("/activities/" + self.id, kwargs)
|
|
605
|
+
if resp:
|
|
606
|
+
for key, value in resp.items():
|
|
607
|
+
if hasattr(self, key):
|
|
608
|
+
setattr(self, key, value)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
_logger.error(f"Error updating activity: {e}")
|
|
611
|
+
return None
|
|
612
|
+
|
|
613
|
+
def add_records(self, record_ids: List[str]) -> None:
|
|
614
|
+
"""Add a list of record IDs to the activity.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
record_ids: A list of record IDs to be added to the activity.
|
|
618
|
+
|
|
619
|
+
Example:
|
|
620
|
+
```python
|
|
621
|
+
activity.add_records(["record_uuid_1", "record_uuid_2"])
|
|
622
|
+
```
|
|
623
|
+
"""
|
|
624
|
+
try:
|
|
625
|
+
self._client._put(
|
|
626
|
+
"/operations/" + self.id + "/records", {"record_ids": record_ids}
|
|
627
|
+
)
|
|
628
|
+
except Exception as e:
|
|
629
|
+
_logger.error(f"Error adding record: {e}")
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
def get_record_data(self) -> List[dict]:
|
|
633
|
+
"""Retrieve data from all this activity's associated records.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
A list containing the activity data for each record,
|
|
637
|
+
obtained by calling get_activity_data with the current activity's UUID.
|
|
638
|
+
|
|
639
|
+
Example:
|
|
640
|
+
```python
|
|
641
|
+
data = activity.get_record_data()
|
|
642
|
+
```
|
|
643
|
+
"""
|
|
644
|
+
data = []
|
|
645
|
+
for record in self.records:
|
|
646
|
+
data.append(record.get_activity_data(self.id))
|
|
647
|
+
return data
|
|
648
|
+
|
|
649
|
+
def refetch(self):
|
|
650
|
+
"""Refreshes all the data of the current activity instance.
|
|
651
|
+
|
|
652
|
+
The activity is also removed from all local caches of its associated client.
|
|
653
|
+
|
|
654
|
+
Automatically called by mutating methods of this activity, but can also be called manually.
|
|
655
|
+
|
|
656
|
+
Example:
|
|
657
|
+
```python
|
|
658
|
+
activity.refetch()
|
|
659
|
+
up_to_date_records = activity.records
|
|
660
|
+
```
|
|
661
|
+
"""
|
|
662
|
+
self._client.activities._clear_activity_caches()
|
|
663
|
+
|
|
664
|
+
new = self._client.activities.get_activity_by_id(self.id)
|
|
665
|
+
|
|
666
|
+
if new is None:
|
|
667
|
+
_logger.error(f"Unable to refresh Activity({self.id})")
|
|
668
|
+
return None
|
|
669
|
+
|
|
670
|
+
for k, v in new.__dict__.items():
|
|
671
|
+
setattr(self, k, v)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
type ActivityIdentifier = Union[Activity, str]
|
|
675
|
+
"""Identifier class for Activity
|
|
676
|
+
|
|
677
|
+
Activities are able to be identified by:
|
|
678
|
+
|
|
679
|
+
* an object instance of an Activity
|
|
680
|
+
* title
|
|
681
|
+
* UUID
|
|
682
|
+
"""
|
|
683
|
+
|
|
684
|
+
type DefinitionIdentifier = Union[ActivityDefinition, str]
|
|
685
|
+
"""Identifier class for ActivityDefinition
|
|
686
|
+
|
|
687
|
+
ActivityDefinitions are able to be identified by:
|
|
688
|
+
|
|
689
|
+
* an object instance of an ActivityDefinition
|
|
690
|
+
* title
|
|
691
|
+
* UUID
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class ActivitiesService:
|
|
696
|
+
"""Service class for managing activities in the Kaleidoscope platform.
|
|
697
|
+
|
|
698
|
+
This service provides methods to create, retrieve, and manage activities
|
|
699
|
+
(tasks/experiments) and their definitions within a Kaleidoscope workspace.
|
|
700
|
+
It handles activity lifecycle operations including creation, retrieval by
|
|
701
|
+
ID or associated records, and batch operations.
|
|
702
|
+
|
|
703
|
+
Note:
|
|
704
|
+
Some methods use LRU caching to improve performance. Cache is cleared on errors.
|
|
705
|
+
"""
|
|
706
|
+
|
|
707
|
+
def __init__(self, client: KaleidoscopeClient):
|
|
708
|
+
self._client = client
|
|
709
|
+
|
|
710
|
+
#########################
|
|
711
|
+
# Public Methods #
|
|
712
|
+
#########################
|
|
713
|
+
|
|
714
|
+
##### for Activities #####
|
|
715
|
+
|
|
716
|
+
@lru_cache
|
|
717
|
+
def get_activities(self) -> List[Activity]:
|
|
718
|
+
"""Retrieve all activities in the workspace, including experiments.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
A list of Activity objects representing the activities
|
|
722
|
+
in the workspace.
|
|
723
|
+
|
|
724
|
+
Note:
|
|
725
|
+
This method caches its results. If an exception occurs, logs the error,
|
|
726
|
+
clears the cache, and returns an empty list.
|
|
727
|
+
|
|
728
|
+
Example:
|
|
729
|
+
```python
|
|
730
|
+
activities = client.activities.get_activities()
|
|
731
|
+
```
|
|
732
|
+
"""
|
|
733
|
+
try:
|
|
734
|
+
resp = self._client._get("/activities")
|
|
735
|
+
return self._create_activity_list(resp)
|
|
736
|
+
except Exception as e:
|
|
737
|
+
_logger.error(f"Error fetching activities: {e}")
|
|
738
|
+
self._clear_activity_caches()
|
|
739
|
+
return []
|
|
740
|
+
|
|
741
|
+
def get_activity_by_type(self, activity_type: ActivityType) -> List[Activity]:
|
|
742
|
+
"""Retrieve all activities of a certain type in the workspace.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
activity_type: The type of `Activity` to retrieve.
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
A list of Activity objects with the type of `activity_type`
|
|
749
|
+
|
|
750
|
+
Example:
|
|
751
|
+
```python
|
|
752
|
+
experiments = client.activities.get_activity_by_type("experiment")
|
|
753
|
+
tasks = client.activities.get_activity_by_type("task")
|
|
754
|
+
```
|
|
755
|
+
"""
|
|
756
|
+
|
|
757
|
+
return [
|
|
758
|
+
act for act in self.get_activities() if act.activity_type == activity_type
|
|
759
|
+
]
|
|
760
|
+
|
|
761
|
+
def get_activity_by_id(self, activity_id: ActivityIdentifier) -> Activity | None:
|
|
762
|
+
"""Retrieve an activity by its identifier.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
activity_id: An identifier of the activity to retrieve.
|
|
766
|
+
|
|
767
|
+
This method will accept and resolve any type of ActivityIdentifier.
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
The Activity object if found, otherwise None.
|
|
771
|
+
|
|
772
|
+
Example:
|
|
773
|
+
```python
|
|
774
|
+
activity = client.activities.get_activity_by_id("activity_uuid")
|
|
775
|
+
```
|
|
776
|
+
"""
|
|
777
|
+
id_to_activity = self._get_activity_id_map()
|
|
778
|
+
identifier = self._resolve_activity_id(activity_id)
|
|
779
|
+
|
|
780
|
+
if identifier is None:
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
return id_to_activity.get(identifier, None)
|
|
784
|
+
|
|
785
|
+
def get_activities_by_ids(self, ids: List[ActivityIdentifier]) -> List[Activity]:
|
|
786
|
+
"""Fetch multiple activities by their identifiers.
|
|
787
|
+
|
|
788
|
+
Args:
|
|
789
|
+
ids: A list of activity identifier strings to fetch.
|
|
790
|
+
|
|
791
|
+
This method will accept and resolve any type of ActivityIdentifier inside the `ids`.
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
A list of Activity objects corresponding to the provided IDs.
|
|
795
|
+
|
|
796
|
+
Note:
|
|
797
|
+
ids that are invalid and return None are not included in the returned list of Activities
|
|
798
|
+
|
|
799
|
+
Example:
|
|
800
|
+
```python
|
|
801
|
+
selected = client.activities.get_activities_by_ids([
|
|
802
|
+
"activity_uuid_1",
|
|
803
|
+
"activity_uuid_2",
|
|
804
|
+
])
|
|
805
|
+
```
|
|
806
|
+
"""
|
|
807
|
+
activities = []
|
|
808
|
+
|
|
809
|
+
for activity_id in ids:
|
|
810
|
+
res = self.get_activity_by_id(activity_id)
|
|
811
|
+
if res:
|
|
812
|
+
activities.append(res)
|
|
813
|
+
|
|
814
|
+
return activities
|
|
815
|
+
|
|
816
|
+
def get_activity_by_external_id(self, external_id: str) -> Activity | None:
|
|
817
|
+
"""Retrieve an activity by its external identifier.
|
|
818
|
+
|
|
819
|
+
Args:
|
|
820
|
+
external_id: The external identifier of the activity to retrieve.
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
The Activity object if found, otherwise None.
|
|
824
|
+
|
|
825
|
+
Example:
|
|
826
|
+
```python
|
|
827
|
+
ext_activity = client.activities.get_activity_by_external_id("jira-123")
|
|
828
|
+
```
|
|
829
|
+
"""
|
|
830
|
+
activities = self.get_activities()
|
|
831
|
+
return next(
|
|
832
|
+
(a for a in activities if a.external_id == external_id),
|
|
833
|
+
None,
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
def create_activity(
|
|
837
|
+
self,
|
|
838
|
+
title: str,
|
|
839
|
+
activity_type: ActivityType,
|
|
840
|
+
program_ids: Optional[list[str]] = None,
|
|
841
|
+
activity_definition_id: Optional[DefinitionIdentifier] = None,
|
|
842
|
+
assigned_user_ids: Optional[List[str]] = None,
|
|
843
|
+
start_date: Optional[datetime] = None,
|
|
844
|
+
duration: Optional[int] = None,
|
|
845
|
+
) -> Activity | None:
|
|
846
|
+
"""Create a new activity.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
title: The title/name of the activity.
|
|
850
|
+
activity_type: The type of activity (e.g. task, experiment, etc.).
|
|
851
|
+
program_ids: List of program IDs to associate with
|
|
852
|
+
the activity. Defaults to None.
|
|
853
|
+
activity_definition_id: Identifier for an activity definition to create the activity with.
|
|
854
|
+
Defaults to None.
|
|
855
|
+
|
|
856
|
+
The identifier will resolve any type of DefinitionIdentifier.
|
|
857
|
+
assigned_user_ids: List of user IDs to assign to
|
|
858
|
+
the activity. Defaults to None.
|
|
859
|
+
start_date: Start date for the activity. Defaults to None.
|
|
860
|
+
duration: Duration in days for the activity. Defaults to None.
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
The newly created activity instance or None if activity
|
|
864
|
+
creation was not successful.
|
|
865
|
+
|
|
866
|
+
Example:
|
|
867
|
+
```python
|
|
868
|
+
new_activity = client.activities.create_activity(
|
|
869
|
+
title="Synthesis",
|
|
870
|
+
activity_type="experiment",
|
|
871
|
+
program_ids=["program_uuid"],
|
|
872
|
+
)
|
|
873
|
+
```
|
|
874
|
+
"""
|
|
875
|
+
self._clear_activity_caches()
|
|
876
|
+
|
|
877
|
+
try:
|
|
878
|
+
payload = {
|
|
879
|
+
"program_ids": program_ids,
|
|
880
|
+
"title": title,
|
|
881
|
+
"activity_type": activity_type,
|
|
882
|
+
"definition_id": self._resolve_definition_id(activity_definition_id),
|
|
883
|
+
"record_ids": [],
|
|
884
|
+
"assigned_user_ids": assigned_user_ids,
|
|
885
|
+
"start_date": start_date.isoformat() if start_date else None,
|
|
886
|
+
"duration": duration,
|
|
887
|
+
}
|
|
888
|
+
resp = self._client._post("/activities", payload)
|
|
889
|
+
return self._create_activity(resp[0])
|
|
890
|
+
except Exception as e:
|
|
891
|
+
_logger.error(f"Error creating activity {title}: {e}")
|
|
892
|
+
return None
|
|
893
|
+
|
|
894
|
+
@cachetools.func.ttl_cache(maxsize=128, ttl=10)
|
|
895
|
+
def get_activities_with_record(self, record_id: RecordIdentifier) -> List[Activity]:
|
|
896
|
+
"""Retrieve all activities that contain a specific record.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
record_id: Identifier for the record.
|
|
900
|
+
|
|
901
|
+
Any type of RecordIdentifier will be accepted.
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
Activities that include the specified record.
|
|
905
|
+
|
|
906
|
+
Note:
|
|
907
|
+
If an exception occurs, logs the error and returns an empty list.
|
|
908
|
+
|
|
909
|
+
Example:
|
|
910
|
+
```python
|
|
911
|
+
activities = client.activities.get_activities_with_record("record_uuid")
|
|
912
|
+
```
|
|
913
|
+
"""
|
|
914
|
+
record_uuid = self._client.records._resolve_to_record_id(record_id)
|
|
915
|
+
if record_uuid is None:
|
|
916
|
+
return []
|
|
917
|
+
|
|
918
|
+
try:
|
|
919
|
+
resp = self._client._get("/records/" + record_uuid + "/operations")
|
|
920
|
+
return self._create_activity_list(resp)
|
|
921
|
+
except Exception as e:
|
|
922
|
+
_logger.error(f"Error fetching activities with record {record_id}: {e}")
|
|
923
|
+
self.get_activities_with_record.cache_clear()
|
|
924
|
+
return []
|
|
925
|
+
|
|
926
|
+
##### for ActivityDefinitions #####
|
|
927
|
+
@lru_cache
|
|
928
|
+
def get_definitions(self) -> List[ActivityDefinition]:
|
|
929
|
+
"""Retrieve all available activity definitions.
|
|
930
|
+
|
|
931
|
+
Returns:
|
|
932
|
+
All activity definitions in the workspace.
|
|
933
|
+
|
|
934
|
+
Raises:
|
|
935
|
+
ValidationError: If the data could not be validated as an ActivityDefinition.
|
|
936
|
+
|
|
937
|
+
Note:
|
|
938
|
+
This method caches its results. If an exception occurs, logs the error,
|
|
939
|
+
clears the cache, and returns an empty list.
|
|
940
|
+
|
|
941
|
+
Example:
|
|
942
|
+
```python
|
|
943
|
+
definitions = client.activities.get_definitions()
|
|
944
|
+
```
|
|
945
|
+
"""
|
|
946
|
+
try:
|
|
947
|
+
resp = self._client._get("/activity_definitions")
|
|
948
|
+
return [self._create_activity_definition(data) for data in resp]
|
|
949
|
+
|
|
950
|
+
except Exception as e:
|
|
951
|
+
_logger.error(f"Error fetching activity definitions: {e}")
|
|
952
|
+
self._clear_definition_caches()
|
|
953
|
+
return []
|
|
954
|
+
|
|
955
|
+
def get_definition_by_id(
|
|
956
|
+
self, definition_id: DefinitionIdentifier
|
|
957
|
+
) -> ActivityDefinition | None:
|
|
958
|
+
"""Retrieve an activity definition by ID (UUID or name)
|
|
959
|
+
|
|
960
|
+
Args:
|
|
961
|
+
definition_id: Identifier for the activity definition.
|
|
962
|
+
|
|
963
|
+
This method will accept and resolve any type of DefinitionIdentifier.
|
|
964
|
+
|
|
965
|
+
Returns:
|
|
966
|
+
The activity definition if found, None otherwise.
|
|
967
|
+
|
|
968
|
+
Example:
|
|
969
|
+
```python
|
|
970
|
+
definition = client.activities.get_definition_by_id("definition_uuid")
|
|
971
|
+
```
|
|
972
|
+
"""
|
|
973
|
+
id_map = self._get_definition_id_map()
|
|
974
|
+
identifier = self._resolve_definition_id(definition_id)
|
|
975
|
+
|
|
976
|
+
if identifier is None:
|
|
977
|
+
return None
|
|
978
|
+
else:
|
|
979
|
+
return id_map.get(identifier, None)
|
|
980
|
+
|
|
981
|
+
def get_definitions_by_ids(
|
|
982
|
+
self, ids: List[DefinitionIdentifier]
|
|
983
|
+
) -> List[ActivityDefinition]:
|
|
984
|
+
"""Retrieve activity definitions by their identifiers
|
|
985
|
+
|
|
986
|
+
Args:
|
|
987
|
+
ids: List of definition identifiers to retrieve.
|
|
988
|
+
|
|
989
|
+
This method will accept and resolve all types of DefinitionIdentifier.
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
List of found activity definitions.
|
|
993
|
+
|
|
994
|
+
Example:
|
|
995
|
+
```python
|
|
996
|
+
defs = client.activities.get_definitions_by_ids(["def1", "def2"])
|
|
997
|
+
```
|
|
998
|
+
"""
|
|
999
|
+
definitions = []
|
|
1000
|
+
|
|
1001
|
+
for definition_id in ids:
|
|
1002
|
+
res = self.get_definition_by_id(definition_id)
|
|
1003
|
+
if res:
|
|
1004
|
+
definitions.append(res)
|
|
1005
|
+
|
|
1006
|
+
return definitions
|
|
1007
|
+
|
|
1008
|
+
def get_activity_definition_by_external_id(
|
|
1009
|
+
self, external_id: str
|
|
1010
|
+
) -> ActivityDefinition | None:
|
|
1011
|
+
"""Retrieve an activity definition by its external identifier.
|
|
1012
|
+
|
|
1013
|
+
Args:
|
|
1014
|
+
external_id: The external identifier of the activity definition to retrieve.
|
|
1015
|
+
|
|
1016
|
+
Returns:
|
|
1017
|
+
The ActivityDefinition object if found, otherwise None.
|
|
1018
|
+
|
|
1019
|
+
Example:
|
|
1020
|
+
```python
|
|
1021
|
+
definition = client.activities.get_activity_definition_by_external_id("jira-def-7")
|
|
1022
|
+
```
|
|
1023
|
+
"""
|
|
1024
|
+
definitions = self.get_definitions()
|
|
1025
|
+
return next(
|
|
1026
|
+
(d for d in definitions if d.external_id == external_id),
|
|
1027
|
+
None,
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
#########################
|
|
1031
|
+
# Private Methods #
|
|
1032
|
+
#########################
|
|
1033
|
+
|
|
1034
|
+
##### for Activities #####
|
|
1035
|
+
|
|
1036
|
+
def _create_activity(self, data: dict) -> Activity:
|
|
1037
|
+
"""Convert a dictionary of activity data into a validated Activity object.
|
|
1038
|
+
|
|
1039
|
+
Args:
|
|
1040
|
+
data: A dictionary containing the activity information.
|
|
1041
|
+
|
|
1042
|
+
Returns:
|
|
1043
|
+
An activity object created from the provided data, with the
|
|
1044
|
+
client set.
|
|
1045
|
+
|
|
1046
|
+
Raises:
|
|
1047
|
+
ValidationError: If the data could not be validated as an Activity.
|
|
1048
|
+
"""
|
|
1049
|
+
activity = Activity.model_validate(data)
|
|
1050
|
+
activity._set_client(self._client)
|
|
1051
|
+
|
|
1052
|
+
return activity
|
|
1053
|
+
|
|
1054
|
+
def _create_activity_list(self, data: list[dict]) -> List[Activity]:
|
|
1055
|
+
"""Convert input data into a list of Activity objects.
|
|
1056
|
+
|
|
1057
|
+
Args:
|
|
1058
|
+
data: The input data to be converted into Activity objects.
|
|
1059
|
+
|
|
1060
|
+
Returns:
|
|
1061
|
+
A list of Activity objects with clients set.
|
|
1062
|
+
|
|
1063
|
+
Raises:
|
|
1064
|
+
ValidationError: If the data could not be validated as a list of
|
|
1065
|
+
Activity objects.
|
|
1066
|
+
"""
|
|
1067
|
+
return [self._create_activity(d) for d in data]
|
|
1068
|
+
|
|
1069
|
+
@lru_cache
|
|
1070
|
+
def _get_activity_id_map(self) -> dict[str, Activity]:
|
|
1071
|
+
"""gets a dict that maps uuids to their corresponding Activity
|
|
1072
|
+
|
|
1073
|
+
Returns:
|
|
1074
|
+
a map of uuid to Activity
|
|
1075
|
+
"""
|
|
1076
|
+
return {activity.id: activity for activity in self.get_activities()}
|
|
1077
|
+
|
|
1078
|
+
@lru_cache
|
|
1079
|
+
def _get_activity_title_map(self) -> dict[str, Activity]:
|
|
1080
|
+
"""gets a dict that maps an activity's title to its object instance
|
|
1081
|
+
|
|
1082
|
+
Returns:
|
|
1083
|
+
str-to-Activity dict that maps titles to Activity
|
|
1084
|
+
"""
|
|
1085
|
+
return {activity.title: activity for activity in self.get_activities()}
|
|
1086
|
+
|
|
1087
|
+
def _resolve_activity_id(self, identifier: ActivityIdentifier | None) -> str | None:
|
|
1088
|
+
"""Resolves an ActivityIdentifier.
|
|
1089
|
+
|
|
1090
|
+
Will get the corresponding uuid of Activity based on the identifier.
|
|
1091
|
+
|
|
1092
|
+
Identifiers will be resolved, while `None` will always return `None`.
|
|
1093
|
+
|
|
1094
|
+
Args:
|
|
1095
|
+
identifier: Identifier for an Activity or None.
|
|
1096
|
+
|
|
1097
|
+
Returns:
|
|
1098
|
+
Returns an Activity's UUID for a valid ActivityIdentifier, else returns None
|
|
1099
|
+
"""
|
|
1100
|
+
if identifier is None:
|
|
1101
|
+
return None
|
|
1102
|
+
|
|
1103
|
+
if isinstance(identifier, Activity):
|
|
1104
|
+
return identifier.id
|
|
1105
|
+
|
|
1106
|
+
id_map = self._get_activity_id_map()
|
|
1107
|
+
if identifier in id_map:
|
|
1108
|
+
return identifier
|
|
1109
|
+
|
|
1110
|
+
name_map = self._get_activity_title_map()
|
|
1111
|
+
activity = name_map.get(identifier)
|
|
1112
|
+
if activity:
|
|
1113
|
+
return activity.id
|
|
1114
|
+
|
|
1115
|
+
_logger.error(f"Activity not found: {identifier}")
|
|
1116
|
+
return None
|
|
1117
|
+
|
|
1118
|
+
def _clear_activity_caches(self):
|
|
1119
|
+
"""Clears all caches of Activity objects
|
|
1120
|
+
|
|
1121
|
+
Call when any activity is created, removed, or updated
|
|
1122
|
+
"""
|
|
1123
|
+
self.get_activities.cache_clear()
|
|
1124
|
+
self._get_activity_id_map.cache_clear()
|
|
1125
|
+
self._get_activity_title_map.cache_clear()
|
|
1126
|
+
|
|
1127
|
+
##### for ActivityDefinitions #####
|
|
1128
|
+
|
|
1129
|
+
def _create_activity_definition(self, data: dict) -> ActivityDefinition:
|
|
1130
|
+
"""Creates an ActivityDefinition based on API data
|
|
1131
|
+
|
|
1132
|
+
Args:
|
|
1133
|
+
data: dict of json data
|
|
1134
|
+
|
|
1135
|
+
Returns:
|
|
1136
|
+
validated ActivityDefinition
|
|
1137
|
+
|
|
1138
|
+
Raises:
|
|
1139
|
+
ValidationError: if data can not be validated
|
|
1140
|
+
"""
|
|
1141
|
+
activity_definition = ActivityDefinition.model_validate(data)
|
|
1142
|
+
activity_definition._set_client(self._client)
|
|
1143
|
+
|
|
1144
|
+
return activity_definition
|
|
1145
|
+
|
|
1146
|
+
@lru_cache
|
|
1147
|
+
def _get_definition_id_map(self) -> dict[str, ActivityDefinition]:
|
|
1148
|
+
"""get a map of uuids to their respective activity definition.
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
A mapping of uuid-to-ActivityDefinition
|
|
1152
|
+
"""
|
|
1153
|
+
return {definition.id: definition for definition in self.get_definitions()}
|
|
1154
|
+
|
|
1155
|
+
@lru_cache
|
|
1156
|
+
def _get_definition_title_map(self) -> dict[str, ActivityDefinition]:
|
|
1157
|
+
"""get a map of an ActivityDefinition's title to their respective ActivityDefinition
|
|
1158
|
+
|
|
1159
|
+
Returns:
|
|
1160
|
+
A mapping of title-to-Activity-Definition
|
|
1161
|
+
"""
|
|
1162
|
+
return {definition.title: definition for definition in self.get_definitions()}
|
|
1163
|
+
|
|
1164
|
+
def _resolve_definition_id(
|
|
1165
|
+
self, identifier: DefinitionIdentifier | None
|
|
1166
|
+
) -> str | None:
|
|
1167
|
+
"""Resolve an ActivityDefinitionIdentifier to its corresponding uuid.
|
|
1168
|
+
|
|
1169
|
+
Will return the corresponding UUID of given identifiers, and will always return `None` if the identifier is `None`.
|
|
1170
|
+
|
|
1171
|
+
Args:
|
|
1172
|
+
identifier: An identifier for ActivityDefinition.
|
|
1173
|
+
|
|
1174
|
+
Returns:
|
|
1175
|
+
Return the corresponding UUID if the identifier is valid, else returns None
|
|
1176
|
+
"""
|
|
1177
|
+
if identifier is None:
|
|
1178
|
+
return None
|
|
1179
|
+
|
|
1180
|
+
if isinstance(identifier, ActivityDefinition):
|
|
1181
|
+
return identifier.id
|
|
1182
|
+
|
|
1183
|
+
id_map = self._get_definition_id_map()
|
|
1184
|
+
if identifier in id_map: # check by uuid
|
|
1185
|
+
return identifier
|
|
1186
|
+
|
|
1187
|
+
name_map = self._get_definition_title_map()
|
|
1188
|
+
definition = name_map.get(identifier)
|
|
1189
|
+
if definition: # check by title
|
|
1190
|
+
return definition.id
|
|
1191
|
+
|
|
1192
|
+
_logger.error(f"Definition not found: {identifier}")
|
|
1193
|
+
return None
|
|
1194
|
+
|
|
1195
|
+
def _clear_definition_caches(self):
|
|
1196
|
+
"""Clears all caches of ActivityDefinition objects
|
|
1197
|
+
|
|
1198
|
+
Call when any activity definition is created, removed, or updated
|
|
1199
|
+
"""
|
|
1200
|
+
self.get_definitions.cache_clear()
|
|
1201
|
+
self._get_definition_id_map.cache_clear()
|
|
1202
|
+
self._get_definition_title_map.cache_clear()
|