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    _link_name = 'access_policy'
 64    _link_key = 'accessPolicyList'
 65    _link_entity = 'AccessPolicy'
 66
 67    def link_access_policy(self, access_policies):
 68        self._add_link('access_policy', access_policies)
 69
 70    def unlink_access_policy(self, access_policies):
 71        self._delete_link('access_policy', access_policies)
 72
 73
 74class ActivityLinker(BaseLinker):
 75    """Subclass can link to Activities"""
 76    _link_name = 'activity'
 77
 78    def link_activity(self, activity):
 79        self._add_link('activity', activity)
 80
 81    def unlink_activity(self, activity):
 82        self._delete_link('activity', activity)
 83
 84
 85class BookingLinker(BaseLinker):
 86    """Subclass can link to Bookings"""
 87    _link_name = 'booking'
 88
 89    def link_booking(self, booking):
 90        self._add_link('booking', booking)
 91
 92    def unlink_booking(self, booking):
 93        self._delete_link('booking', booking)
 94
 95
 96class CompanyLinker(BaseLinker):
 97    """Subclass can link to Companies"""
 98    _link_name = 'company'
 99
100    def link_company(self, companies):
101        self._add_link('company', companies)
102
103    def unlink_company(self, companies):
104        self._delete_link('company', companies)
105
106
107class ContactLinker(BaseLinker):
108    """Subclass can link to Contacts"""
109    _link_name = 'contact'
110
111    def link_contact(self, contacts):
112        self._add_link('contact', contacts)
113
114    def unlink_contact(self, contacts):
115        self._delete_link('contact', contacts)
116
117
118class CustomerLinker(BaseLinker):
119    """Subclass can link to Customers"""
120    _link_name = 'customer'
121
122    def link_customer(self, customers):
123        self._add_link('customer', customers)
124
125    def unlink_customer(self, customers):
126        self._delete_link('customer', customers)
127
128
129class DepartmentLinker(BaseLinker):
130    """Subclass can link to Departments"""
131    _link_name = 'department'
132
133    def link_department(self, departments):
134        self._add_link('department', departments)
135
136    def unlink_department(self, departments):
137        self._delete_link('department', departments)
138
139
140class FileLinker(BaseLinker):
141    """Subclass can link to Files"""
142    _link_name = 'file'
143    _link_key = 'storageFileList'
144
145    def link_file(self, files):
146        self._add_link('file', files)
147
148    def unlink_file(self, files):
149        self._delete_link('file', files)
150
151
152class FolderLinker(BaseLinker):
153    """Subclass can link to Folders"""
154    _link_name = 'folder'
155    _link_key = 'folders'
156
157    def link_folder(self, folders):
158        self._add_link('folder', folders)
159
160    def unlink_folder(self, folders):
161        self._delete_link('folder', folders)
162
163
164class LocationLinker(BaseLinker):
165    """Subclass can link to Locations"""
166    _link_name = 'location'
167
168    def link_location(self, locations):
169        self._add_link('location', locations)
170
171    def unlink_location(self, locations):
172        self._delete_link('location', locations)
173
174
175class PermissionLinker(BaseLinker):
176    """Subclass can link to Permissions"""
177    _link_name = 'permission'
178
179    def link_permission(self, permissions):
180        return self._add_link('permission', permissions)
181
182    def unlink_permission(self, permissions):
183        return self._delete_link('permission', permissions)
184
185
186class ProjectLinker(BaseLinker):
187    """Subclass can link to Projects"""
188    _link_name = 'project'
189
190    def link_project(self, projects):
191        self._add_link('project', projects)
192
193    def unlink_project(self, projects):
194        self._delete_link('project', projects)
195
196
197class RebateLinker(BaseLinker):
198    """Subclass can link to Rebates"""
199    _link_name = 'rebate'
200
201    def link_rebate(self, rebates):
202        self._add_link('rebate', rebates)
203
204    def unlink_rebate(self, rebates):
205        self._delete_link('rebate', rebates)
206
207
208class ResourceLinker(BaseLinker):
209    """Subclass can link to Resources"""
210    _link_name = 'resource'
211    _link_data_name = 'resourceLink'
212
213    def link_resource(self, resources):
214        self._add_link('resource', resources)
215
216    def relink_resource(self, resources):
217        self._update_link('resource', resources)
218
219    def unlink_resource(self, resources):
220        self._delete_link('resource', resources)
221
222
223class SkillLinker(BaseLinker):
224    """Subclass can link to Skills"""
225    _link_name = 'skill'
226    _link_data_name = 'skillLink'
227
228    def link_skill(self, skills):
229        self._add_link('skill', skills)
230
231    def relink_skill(self, skills):
232        self._update_link('skill', skills)
233
234    def unlink_skill(self, skills):
235        self._delete_link('skill', skills)
236
237
238class StaffLinker(BaseLinker):
239    """Subclass can link to Staff"""
240    _link_name = 'staff'
241    _link_data_name = 'resourceLink'
242
243    def link_staff(self, staffs):
244        self._add_link('staff', staffs)
245
246    def relink_staff(self, staffs):
247        self._update_link('staff', staffs)
248
249    def unlink_staff(self, staffs):
250        self._delete_link('staff', staffs)
251
252
253class StageLinker(BaseLinker):
254    """Subclass can link to Stages"""
255    _link_name = 'stage'
256    _link_key = 'stage'
257    _link_type = dict
258
259    def link_stage(self, stages):
260        self._add_link('stage', stages)
261
262    def unlink_stage(self, stages):
263        self._delete_link('stage', stages)
264
265
266class StageListLinker(BaseLinker):
267    """Subclass can link to Stage List"""
268    _link_name = 'stage_list'
269    _link_key = 'stageList'
270    _link_entity = 'Stage'
271
272    def link_stage_list(self, stages):
273        if not isinstance(stages, list):
274            raise api.UsageException('Stage list link must be a list')
275        self._add_link('stage_list', stages)
276
277    def unlink_stage_list(self, stages):
278        if not isinstance(stages, list):
279            raise api.UsageException('Stage list unlink must be a list')
280        stages = [{'uuId': s['uuId']} for s in stages]
281        self._delete_link('stage_list', stages)
282
283
284class UserLinker(BaseLinker):
285    _link_name = 'user'
286
287    def link_user(self, users):
288        self._add_link('user', users)
289
290    def unlink_user(self, users):
291        self._delete_link('user', users)
292
293
294class TaskLinker(BaseLinker):
295    _link_name = 'task'
296
297    def link_task(self, tasks):
298        self._add_link('task', tasks)
299
300    def unlink_task(self, tasks):
301        self._delete_link('task', tasks)
302
303
304class TaskTemplateLinker(BaseLinker):
305    _link_name = 'task_template'
306    _link_entity = 'TaskTemplate'
307
308    def link_task_template(self, task_templates):
309        self._add_link('task_template', task_templates)
310
311    def unlink_task_template(self, task_templates):
312        self._delete_link('task_template', task_templates)
313
314class TagLinker(BaseLinker):
315    _link_name = 'tag'
316
317    def link_tag(self, tags):
318        self._add_link('tag', tags)
319
320    def unlink_tag(self, tags):
321        self._delete_link('tag', tags)
322
323class NoteLinker(BaseLinker):
324    _link_name = 'note'
325
326class CalendarLinker(BaseLinker):
327    _link_name = 'calendar'
328
329# Projects have a list of tasks that we can fetch using the links=
330# method, but they have no linker methods available.
331class TaskInProjectLinker(BaseLinker):
332    _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    _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)

Subclass can link to Access Policies

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

Subclass can link to Activities

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

Subclass can link to Bookings

class CompanyLinker(BaseLinker):
 97class CompanyLinker(BaseLinker):
 98    """Subclass can link to Companies"""
 99    _link_name = 'company'
100
101    def link_company(self, companies):
102        self._add_link('company', companies)
103
104    def unlink_company(self, companies):
105        self._delete_link('company', companies)

Subclass can link to Companies

class ContactLinker(BaseLinker):
108class ContactLinker(BaseLinker):
109    """Subclass can link to Contacts"""
110    _link_name = 'contact'
111
112    def link_contact(self, contacts):
113        self._add_link('contact', contacts)
114
115    def unlink_contact(self, contacts):
116        self._delete_link('contact', contacts)

Subclass can link to Contacts

class CustomerLinker(BaseLinker):
119class CustomerLinker(BaseLinker):
120    """Subclass can link to Customers"""
121    _link_name = 'customer'
122
123    def link_customer(self, customers):
124        self._add_link('customer', customers)
125
126    def unlink_customer(self, customers):
127        self._delete_link('customer', customers)

Subclass can link to Customers

class DepartmentLinker(BaseLinker):
130class DepartmentLinker(BaseLinker):
131    """Subclass can link to Departments"""
132    _link_name = 'department'
133
134    def link_department(self, departments):
135        self._add_link('department', departments)
136
137    def unlink_department(self, departments):
138        self._delete_link('department', departments)

Subclass can link to Departments

class FileLinker(BaseLinker):
141class FileLinker(BaseLinker):
142    """Subclass can link to Files"""
143    _link_name = 'file'
144    _link_key = 'storageFileList'
145
146    def link_file(self, files):
147        self._add_link('file', files)
148
149    def unlink_file(self, files):
150        self._delete_link('file', files)

Subclass can link to Files

class FolderLinker(BaseLinker):
153class FolderLinker(BaseLinker):
154    """Subclass can link to Folders"""
155    _link_name = 'folder'
156    _link_key = 'folders'
157
158    def link_folder(self, folders):
159        self._add_link('folder', folders)
160
161    def unlink_folder(self, folders):
162        self._delete_link('folder', folders)

Subclass can link to Folders

class LocationLinker(BaseLinker):
165class LocationLinker(BaseLinker):
166    """Subclass can link to Locations"""
167    _link_name = 'location'
168
169    def link_location(self, locations):
170        self._add_link('location', locations)
171
172    def unlink_location(self, locations):
173        self._delete_link('location', locations)

Subclass can link to Locations

class PermissionLinker(BaseLinker):
176class PermissionLinker(BaseLinker):
177    """Subclass can link to Permissions"""
178    _link_name = 'permission'
179
180    def link_permission(self, permissions):
181        return self._add_link('permission', permissions)
182
183    def unlink_permission(self, permissions):
184        return self._delete_link('permission', permissions)

Subclass can link to Permissions

class ProjectLinker(BaseLinker):
187class ProjectLinker(BaseLinker):
188    """Subclass can link to Projects"""
189    _link_name = 'project'
190
191    def link_project(self, projects):
192        self._add_link('project', projects)
193
194    def unlink_project(self, projects):
195        self._delete_link('project', projects)

Subclass can link to Projects

class RebateLinker(BaseLinker):
198class RebateLinker(BaseLinker):
199    """Subclass can link to Rebates"""
200    _link_name = 'rebate'
201
202    def link_rebate(self, rebates):
203        self._add_link('rebate', rebates)
204
205    def unlink_rebate(self, rebates):
206        self._delete_link('rebate', rebates)

Subclass can link to Rebates

class ResourceLinker(BaseLinker):
209class ResourceLinker(BaseLinker):
210    """Subclass can link to Resources"""
211    _link_name = 'resource'
212    _link_data_name = 'resourceLink'
213
214    def link_resource(self, resources):
215        self._add_link('resource', resources)
216
217    def relink_resource(self, resources):
218        self._update_link('resource', resources)
219
220    def unlink_resource(self, resources):
221        self._delete_link('resource', resources)

Subclass can link to Resources

class SkillLinker(BaseLinker):
224class SkillLinker(BaseLinker):
225    """Subclass can link to Skills"""
226    _link_name = 'skill'
227    _link_data_name = 'skillLink'
228
229    def link_skill(self, skills):
230        self._add_link('skill', skills)
231
232    def relink_skill(self, skills):
233        self._update_link('skill', skills)
234
235    def unlink_skill(self, skills):
236        self._delete_link('skill', skills)

Subclass can link to Skills

class StaffLinker(BaseLinker):
239class StaffLinker(BaseLinker):
240    """Subclass can link to Staff"""
241    _link_name = 'staff'
242    _link_data_name = 'resourceLink'
243
244    def link_staff(self, staffs):
245        self._add_link('staff', staffs)
246
247    def relink_staff(self, staffs):
248        self._update_link('staff', staffs)
249
250    def unlink_staff(self, staffs):
251        self._delete_link('staff', staffs)

Subclass can link to Staff

class StageLinker(BaseLinker):
254class StageLinker(BaseLinker):
255    """Subclass can link to Stages"""
256    _link_name = 'stage'
257    _link_key = 'stage'
258    _link_type = dict
259
260    def link_stage(self, stages):
261        self._add_link('stage', stages)
262
263    def unlink_stage(self, stages):
264        self._delete_link('stage', stages)

Subclass can link to Stages

class StageListLinker(BaseLinker):
267class StageListLinker(BaseLinker):
268    """Subclass can link to Stage List"""
269    _link_name = 'stage_list'
270    _link_key = 'stageList'
271    _link_entity = 'Stage'
272
273    def link_stage_list(self, stages):
274        if not isinstance(stages, list):
275            raise api.UsageException('Stage list link must be a list')
276        self._add_link('stage_list', stages)
277
278    def unlink_stage_list(self, stages):
279        if not isinstance(stages, list):
280            raise api.UsageException('Stage list unlink must be a list')
281        stages = [{'uuId': s['uuId']} for s in stages]
282        self._delete_link('stage_list', stages)

Subclass can link to Stage List

class UserLinker(BaseLinker):
285class UserLinker(BaseLinker):
286    _link_name = 'user'
287
288    def link_user(self, users):
289        self._add_link('user', users)
290
291    def unlink_user(self, users):
292        self._delete_link('user', users)
class TaskLinker(BaseLinker):
295class TaskLinker(BaseLinker):
296    _link_name = 'task'
297
298    def link_task(self, tasks):
299        self._add_link('task', tasks)
300
301    def unlink_task(self, tasks):
302        self._delete_link('task', tasks)
class TaskTemplateLinker(BaseLinker):
305class TaskTemplateLinker(BaseLinker):
306    _link_name = 'task_template'
307    _link_entity = 'TaskTemplate'
308
309    def link_task_template(self, task_templates):
310        self._add_link('task_template', task_templates)
311
312    def unlink_task_template(self, task_templates):
313        self._delete_link('task_template', task_templates)
class TagLinker(BaseLinker):
315class TagLinker(BaseLinker):
316    _link_name = 'tag'
317
318    def link_tag(self, tags):
319        self._add_link('tag', tags)
320
321    def unlink_tag(self, tags):
322        self._delete_link('tag', tags)
class NoteLinker(BaseLinker):
324class NoteLinker(BaseLinker):
325    _link_name = 'note'
class CalendarLinker(BaseLinker):
327class CalendarLinker(BaseLinker):
328    _link_name = 'calendar'
class TaskInProjectLinker(BaseLinker):
332class TaskInProjectLinker(BaseLinker):
333    _link_name = "task"