projectal.linkers

Linkers provide the interface to add/update/delete links between entities. Only certain entities can link to certain other entities. When using this library, your tooling should show you which link methods are available to the Entity subclass you are using.

Note that some links require additional data in the link. You must ensure the destination object has this data before adding or updating the link (example below). See the API documentation for exact link details. Missing data will raise a projectal.errors.ProjectalException with information about what is missing.

An instance of an entity class that inherits from a linker is able to link to an instance of the target entity class directly.

# Get task and staff
task = projectal.Task.get('1b21e445-f29a...')
staff = projectal.Staff.get('1b21e445-f29a...')

# Task-to-Staff links require a 'resourceLink'
staff['resourceLink'] = {'utilization': 0.6}

# Task inherits from StaffLinker, so staff linking available
task.link_staff(staff)
  1"""
  2Linkers provide the interface to add/update/delete links between entities.
  3Only certain entities can link to certain other entities. When using this
  4library, your tooling should show you which link methods are available
  5to the Entity subclass you are using.
  6
  7Note that some links require additional data in the link. You must ensure
  8the destination object has this data before adding or updating the link
  9(example below). See the API documentation for exact link details.
 10Missing data will raise a `projectal.errors.ProjectalException` with
 11information about what is missing.
 12
 13
 14An instance of an entity class that inherits from a linker is able to link
 15to an instance of the target entity class directly.
 16
 17```
 18# Get task and staff
 19task = projectal.Task.get('1b21e445-f29a...')
 20staff = projectal.Staff.get('1b21e445-f29a...')
 21
 22# Task-to-Staff links require a 'resourceLink'
 23staff['resourceLink'] = {'utilization': 0.6}
 24
 25# Task inherits from StaffLinker, so staff linking available
 26task.link_staff(staff)
 27```
 28
 29"""
 30
 31from projectal import api
 32
 33
 34class BaseLinker:
 35    # _link_name is the link name (usually the entity name)
 36    _link_name = None
 37
 38    # _link_key is the key within the source object that points to the
 39    # links. E.g., 'skillList'
 40    _link_key = None
 41
 42    # _link_data_name is the key within the linked (target) object that points
 43    # to a store of custom values within that link. E.g, Skill objects,
 44    # when linked, have a 'skillLink' property that holds data about
 45    # the link.
 46    _link_data_name = None
 47
 48    # _link_type is the data type of the value in entity[link_key]. This is
 49    # usually a list since most links appear as 'skillList', 'staffList',
 50    # etc. But some links are single-entity only and appear as dicts like
 51    # project[stage] = Stage.
 52    _link_type = list
 53
 54    # _link_entity is the string name (capitalized, like Stage) of the Entity
 55    # class within this library that fetched links will be converted to.
 56    # This is useful when the name of the list differs from the entity
 57    # name. E.g: stage_list needs to be converted to Stage.
 58    _link_entity = None
 59
 60
 61class AccessPolicyLinker(BaseLinker):
 62    """Subclass can link to Access Policies"""
 63
 64    _link_name = "access_policy"
 65    _link_key = "accessPolicyList"
 66    _link_entity = "AccessPolicy"
 67
 68    def link_access_policy(self, access_policies):
 69        self._add_link("access_policy", access_policies)
 70
 71    def unlink_access_policy(self, access_policies):
 72        self._delete_link("access_policy", access_policies)
 73
 74
 75class ActivityLinker(BaseLinker):
 76    """Subclass can link to Activities"""
 77
 78    _link_name = "activity"
 79
 80    def link_activity(self, activity):
 81        self._add_link("activity", activity)
 82
 83    def unlink_activity(self, activity):
 84        self._delete_link("activity", activity)
 85
 86
 87class BookingLinker(BaseLinker):
 88    """Subclass can link to Bookings"""
 89
 90    _link_name = "booking"
 91
 92    def link_booking(self, booking):
 93        self._add_link("booking", booking)
 94
 95    def unlink_booking(self, booking):
 96        self._delete_link("booking", booking)
 97
 98
 99class CompanyLinker(BaseLinker):
100    """Subclass can link to Companies"""
101
102    _link_name = "company"
103
104    def link_company(self, companies):
105        self._add_link("company", companies)
106
107    def unlink_company(self, companies):
108        self._delete_link("company", companies)
109
110
111class ContactLinker(BaseLinker):
112    """Subclass can link to Contacts"""
113
114    _link_name = "contact"
115
116    def link_contact(self, contacts):
117        self._add_link("contact", contacts)
118
119    def unlink_contact(self, contacts):
120        self._delete_link("contact", contacts)
121
122
123class CustomerLinker(BaseLinker):
124    """Subclass can link to Customers"""
125
126    _link_name = "customer"
127
128    def link_customer(self, customers):
129        self._add_link("customer", customers)
130
131    def unlink_customer(self, customers):
132        self._delete_link("customer", customers)
133
134
135class DepartmentLinker(BaseLinker):
136    """Subclass can link to Departments"""
137
138    _link_name = "department"
139
140    def link_department(self, departments):
141        self._add_link("department", departments)
142
143    def unlink_department(self, departments):
144        self._delete_link("department", departments)
145
146
147class FileLinker(BaseLinker):
148    """Subclass can link to Files"""
149
150    _link_name = "file"
151    _link_key = "storageFileList"
152
153    def link_file(self, files):
154        self._add_link("file", files)
155
156    def unlink_file(self, files):
157        self._delete_link("file", files)
158
159
160class FolderLinker(BaseLinker):
161    """Subclass can link to Folders"""
162
163    _link_name = "folder"
164    _link_key = "folders"
165
166    def link_folder(self, folders):
167        self._add_link("folder", folders)
168
169    def unlink_folder(self, folders):
170        self._delete_link("folder", folders)
171
172
173class LocationLinker(BaseLinker):
174    """Subclass can link to Locations"""
175
176    _link_name = "location"
177
178    def link_location(self, locations):
179        self._add_link("location", locations)
180
181    def unlink_location(self, locations):
182        self._delete_link("location", locations)
183
184
185class PermissionLinker(BaseLinker):
186    """Subclass can link to Permissions"""
187
188    _link_name = "permission"
189
190    def link_permission(self, permissions):
191        return self._add_link("permission", permissions)
192
193    def unlink_permission(self, permissions):
194        return self._delete_link("permission", permissions)
195
196
197class ProjectLinker(BaseLinker):
198    """Subclass can link to Projects"""
199
200    _link_name = "project"
201
202    def link_project(self, projects):
203        self._add_link("project", projects)
204
205    def unlink_project(self, projects):
206        self._delete_link("project", projects)
207
208
209class RebateLinker(BaseLinker):
210    """Subclass can link to Rebates"""
211
212    _link_name = "rebate"
213
214    def link_rebate(self, rebates):
215        self._add_link("rebate", rebates)
216
217    def unlink_rebate(self, rebates):
218        self._delete_link("rebate", rebates)
219
220
221class ResourceLinker(BaseLinker):
222    """Subclass can link to Resources"""
223
224    _link_name = "resource"
225    _link_data_name = "resourceLink"
226
227    def link_resource(self, resources):
228        self._add_link("resource", resources)
229
230    def relink_resource(self, resources):
231        self._update_link("resource", resources)
232
233    def unlink_resource(self, resources):
234        self._delete_link("resource", resources)
235
236
237class SkillLinker(BaseLinker):
238    """Subclass can link to Skills"""
239
240    _link_name = "skill"
241    _link_data_name = "skillLink"
242
243    def link_skill(self, skills):
244        self._add_link("skill", skills)
245
246    def relink_skill(self, skills):
247        self._update_link("skill", skills)
248
249    def unlink_skill(self, skills):
250        self._delete_link("skill", skills)
251
252
253class StaffLinker(BaseLinker):
254    """Subclass can link to Staff"""
255
256    _link_name = "staff"
257    _link_data_name = "resourceLink"
258
259    def link_staff(self, staffs):
260        self._add_link("staff", staffs)
261
262    def relink_staff(self, staffs):
263        self._update_link("staff", staffs)
264
265    def unlink_staff(self, staffs):
266        self._delete_link("staff", staffs)
267
268
269class StageLinker(BaseLinker):
270    """Subclass can link to Stages"""
271
272    _link_name = "stage"
273    _link_key = "stage"
274    _link_type = dict
275
276    def link_stage(self, stages):
277        self._add_link("stage", stages)
278
279    def unlink_stage(self, stages):
280        self._delete_link("stage", stages)
281
282
283class StageListLinker(BaseLinker):
284    """Subclass can link to Stage List"""
285
286    _link_name = "stage_list"
287    _link_key = "stageList"
288    _link_entity = "Stage"
289
290    def link_stage_list(self, stages):
291        if not isinstance(stages, list):
292            raise api.UsageException("Stage list link must be a list")
293        self._add_link("stage_list", stages)
294
295    def unlink_stage_list(self, stages):
296        if not isinstance(stages, list):
297            raise api.UsageException("Stage list unlink must be a list")
298        stages = [{"uuId": s["uuId"]} for s in stages]
299        self._delete_link("stage_list", stages)
300
301
302class UserLinker(BaseLinker):
303    _link_name = "user"
304
305    def link_user(self, users):
306        self._add_link("user", users)
307
308    def unlink_user(self, users):
309        self._delete_link("user", users)
310
311
312class TaskLinker(BaseLinker):
313    _link_name = "task"
314
315    def link_task(self, tasks):
316        self._add_link("task", tasks)
317
318    def unlink_task(self, tasks):
319        self._delete_link("task", tasks)
320
321
322class TaskTemplateLinker(BaseLinker):
323    _link_name = "task_template"
324    _link_entity = "TaskTemplate"
325
326    def link_task_template(self, task_templates):
327        self._add_link("task_template", task_templates)
328
329    def unlink_task_template(self, task_templates):
330        self._delete_link("task_template", task_templates)
331
332
333class TagLinker(BaseLinker):
334    _link_name = "tag"
335
336    def link_tag(self, tags):
337        self._add_link("tag", tags)
338
339    def unlink_tag(self, tags):
340        self._delete_link("tag", tags)
341
342
343class NoteLinker(BaseLinker):
344    _link_name = "note"
345
346
347class CalendarLinker(BaseLinker):
348    _link_name = "calendar"
349
350
351# Projects have a list of tasks that we can fetch using the links=
352# method, but they have no linker methods available.
353class TaskInProjectLinker(BaseLinker):
354    _link_name = "task"
class BaseLinker:
35class BaseLinker:
36    # _link_name is the link name (usually the entity name)
37    _link_name = None
38
39    # _link_key is the key within the source object that points to the
40    # links. E.g., 'skillList'
41    _link_key = None
42
43    # _link_data_name is the key within the linked (target) object that points
44    # to a store of custom values within that link. E.g, Skill objects,
45    # when linked, have a 'skillLink' property that holds data about
46    # the link.
47    _link_data_name = None
48
49    # _link_type is the data type of the value in entity[link_key]. This is
50    # usually a list since most links appear as 'skillList', 'staffList',
51    # etc. But some links are single-entity only and appear as dicts like
52    # project[stage] = Stage.
53    _link_type = list
54
55    # _link_entity is the string name (capitalized, like Stage) of the Entity
56    # class within this library that fetched links will be converted to.
57    # This is useful when the name of the list differs from the entity
58    # name. E.g: stage_list needs to be converted to Stage.
59    _link_entity = None
class AccessPolicyLinker(BaseLinker):
62class AccessPolicyLinker(BaseLinker):
63    """Subclass can link to Access Policies"""
64
65    _link_name = "access_policy"
66    _link_key = "accessPolicyList"
67    _link_entity = "AccessPolicy"
68
69    def link_access_policy(self, access_policies):
70        self._add_link("access_policy", access_policies)
71
72    def unlink_access_policy(self, access_policies):
73        self._delete_link("access_policy", access_policies)

Subclass can link to Access Policies

class ActivityLinker(BaseLinker):
76class ActivityLinker(BaseLinker):
77    """Subclass can link to Activities"""
78
79    _link_name = "activity"
80
81    def link_activity(self, activity):
82        self._add_link("activity", activity)
83
84    def unlink_activity(self, activity):
85        self._delete_link("activity", activity)

Subclass can link to Activities

class BookingLinker(BaseLinker):
88class BookingLinker(BaseLinker):
89    """Subclass can link to Bookings"""
90
91    _link_name = "booking"
92
93    def link_booking(self, booking):
94        self._add_link("booking", booking)
95
96    def unlink_booking(self, booking):
97        self._delete_link("booking", booking)

Subclass can link to Bookings

class CompanyLinker(BaseLinker):
100class CompanyLinker(BaseLinker):
101    """Subclass can link to Companies"""
102
103    _link_name = "company"
104
105    def link_company(self, companies):
106        self._add_link("company", companies)
107
108    def unlink_company(self, companies):
109        self._delete_link("company", companies)

Subclass can link to Companies

class ContactLinker(BaseLinker):
112class ContactLinker(BaseLinker):
113    """Subclass can link to Contacts"""
114
115    _link_name = "contact"
116
117    def link_contact(self, contacts):
118        self._add_link("contact", contacts)
119
120    def unlink_contact(self, contacts):
121        self._delete_link("contact", contacts)

Subclass can link to Contacts

class CustomerLinker(BaseLinker):
124class CustomerLinker(BaseLinker):
125    """Subclass can link to Customers"""
126
127    _link_name = "customer"
128
129    def link_customer(self, customers):
130        self._add_link("customer", customers)
131
132    def unlink_customer(self, customers):
133        self._delete_link("customer", customers)

Subclass can link to Customers

class DepartmentLinker(BaseLinker):
136class DepartmentLinker(BaseLinker):
137    """Subclass can link to Departments"""
138
139    _link_name = "department"
140
141    def link_department(self, departments):
142        self._add_link("department", departments)
143
144    def unlink_department(self, departments):
145        self._delete_link("department", departments)

Subclass can link to Departments

class FileLinker(BaseLinker):
148class FileLinker(BaseLinker):
149    """Subclass can link to Files"""
150
151    _link_name = "file"
152    _link_key = "storageFileList"
153
154    def link_file(self, files):
155        self._add_link("file", files)
156
157    def unlink_file(self, files):
158        self._delete_link("file", files)

Subclass can link to Files

class FolderLinker(BaseLinker):
161class FolderLinker(BaseLinker):
162    """Subclass can link to Folders"""
163
164    _link_name = "folder"
165    _link_key = "folders"
166
167    def link_folder(self, folders):
168        self._add_link("folder", folders)
169
170    def unlink_folder(self, folders):
171        self._delete_link("folder", folders)

Subclass can link to Folders

class LocationLinker(BaseLinker):
174class LocationLinker(BaseLinker):
175    """Subclass can link to Locations"""
176
177    _link_name = "location"
178
179    def link_location(self, locations):
180        self._add_link("location", locations)
181
182    def unlink_location(self, locations):
183        self._delete_link("location", locations)

Subclass can link to Locations

class PermissionLinker(BaseLinker):
186class PermissionLinker(BaseLinker):
187    """Subclass can link to Permissions"""
188
189    _link_name = "permission"
190
191    def link_permission(self, permissions):
192        return self._add_link("permission", permissions)
193
194    def unlink_permission(self, permissions):
195        return self._delete_link("permission", permissions)

Subclass can link to Permissions

class ProjectLinker(BaseLinker):
198class ProjectLinker(BaseLinker):
199    """Subclass can link to Projects"""
200
201    _link_name = "project"
202
203    def link_project(self, projects):
204        self._add_link("project", projects)
205
206    def unlink_project(self, projects):
207        self._delete_link("project", projects)

Subclass can link to Projects

class RebateLinker(BaseLinker):
210class RebateLinker(BaseLinker):
211    """Subclass can link to Rebates"""
212
213    _link_name = "rebate"
214
215    def link_rebate(self, rebates):
216        self._add_link("rebate", rebates)
217
218    def unlink_rebate(self, rebates):
219        self._delete_link("rebate", rebates)

Subclass can link to Rebates

class ResourceLinker(BaseLinker):
222class ResourceLinker(BaseLinker):
223    """Subclass can link to Resources"""
224
225    _link_name = "resource"
226    _link_data_name = "resourceLink"
227
228    def link_resource(self, resources):
229        self._add_link("resource", resources)
230
231    def relink_resource(self, resources):
232        self._update_link("resource", resources)
233
234    def unlink_resource(self, resources):
235        self._delete_link("resource", resources)

Subclass can link to Resources

class SkillLinker(BaseLinker):
238class SkillLinker(BaseLinker):
239    """Subclass can link to Skills"""
240
241    _link_name = "skill"
242    _link_data_name = "skillLink"
243
244    def link_skill(self, skills):
245        self._add_link("skill", skills)
246
247    def relink_skill(self, skills):
248        self._update_link("skill", skills)
249
250    def unlink_skill(self, skills):
251        self._delete_link("skill", skills)

Subclass can link to Skills

class StaffLinker(BaseLinker):
254class StaffLinker(BaseLinker):
255    """Subclass can link to Staff"""
256
257    _link_name = "staff"
258    _link_data_name = "resourceLink"
259
260    def link_staff(self, staffs):
261        self._add_link("staff", staffs)
262
263    def relink_staff(self, staffs):
264        self._update_link("staff", staffs)
265
266    def unlink_staff(self, staffs):
267        self._delete_link("staff", staffs)

Subclass can link to Staff

class StageLinker(BaseLinker):
270class StageLinker(BaseLinker):
271    """Subclass can link to Stages"""
272
273    _link_name = "stage"
274    _link_key = "stage"
275    _link_type = dict
276
277    def link_stage(self, stages):
278        self._add_link("stage", stages)
279
280    def unlink_stage(self, stages):
281        self._delete_link("stage", stages)

Subclass can link to Stages

class StageListLinker(BaseLinker):
284class StageListLinker(BaseLinker):
285    """Subclass can link to Stage List"""
286
287    _link_name = "stage_list"
288    _link_key = "stageList"
289    _link_entity = "Stage"
290
291    def link_stage_list(self, stages):
292        if not isinstance(stages, list):
293            raise api.UsageException("Stage list link must be a list")
294        self._add_link("stage_list", stages)
295
296    def unlink_stage_list(self, stages):
297        if not isinstance(stages, list):
298            raise api.UsageException("Stage list unlink must be a list")
299        stages = [{"uuId": s["uuId"]} for s in stages]
300        self._delete_link("stage_list", stages)

Subclass can link to Stage List

class UserLinker(BaseLinker):
303class UserLinker(BaseLinker):
304    _link_name = "user"
305
306    def link_user(self, users):
307        self._add_link("user", users)
308
309    def unlink_user(self, users):
310        self._delete_link("user", users)
class TaskLinker(BaseLinker):
313class TaskLinker(BaseLinker):
314    _link_name = "task"
315
316    def link_task(self, tasks):
317        self._add_link("task", tasks)
318
319    def unlink_task(self, tasks):
320        self._delete_link("task", tasks)
class TaskTemplateLinker(BaseLinker):
323class TaskTemplateLinker(BaseLinker):
324    _link_name = "task_template"
325    _link_entity = "TaskTemplate"
326
327    def link_task_template(self, task_templates):
328        self._add_link("task_template", task_templates)
329
330    def unlink_task_template(self, task_templates):
331        self._delete_link("task_template", task_templates)
class TagLinker(BaseLinker):
334class TagLinker(BaseLinker):
335    _link_name = "tag"
336
337    def link_tag(self, tags):
338        self._add_link("tag", tags)
339
340    def unlink_tag(self, tags):
341        self._delete_link("tag", tags)
class NoteLinker(BaseLinker):
344class NoteLinker(BaseLinker):
345    _link_name = "note"
class CalendarLinker(BaseLinker):
348class CalendarLinker(BaseLinker):
349    _link_name = "calendar"
class TaskInProjectLinker(BaseLinker):
354class TaskInProjectLinker(BaseLinker):
355    _link_name = "task"