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
 33class BaseLinker:
 34    # _link_name is the link name (usually the entity name)
 35    _link_name = None
 36
 37    # _link_key is the key within the source object that points to the
 38    # links. E.g., 'skillList'
 39    _link_key = None
 40
 41    # _link_data_name is the key within the linked (target) object that points
 42    # to a store of custom values within that link. E.g, Skill objects,
 43    # when linked, have a 'skillLink' property that holds data about
 44    # the link.
 45    _link_data_name = None
 46
 47    # _link_type is the data type of the value in entity[link_key]. This is
 48    # usually a list since most links appear as 'skillList', 'staffList',
 49    # etc. But some links are single-entity only and appear as dicts like
 50    # project[stage] = Stage.
 51    _link_type = list
 52
 53    # _link_entity is the string name (capitalized, like Stage) of the Entity
 54    # class within this library that fetched links will be converted to.
 55    # This is useful when the name of the list differs from the entity
 56    # name. E.g: stage_list needs to be converted to Stage.
 57    _link_entity = None
 58
 59class AccessPolicyLinker(BaseLinker):
 60    """Subclass can link to Access Policies"""
 61    _link_name = 'access_policy'
 62    _link_key = 'accessPolicyList'
 63    _link_entity = 'AccessPolicy'
 64
 65    def link_access_policy(self, access_policies):
 66        self._add_link('access_policy', access_policies)
 67
 68    def unlink_access_policy(self, access_policies):
 69        self._delete_link('access_policy', access_policies)
 70
 71
 72class CompanyLinker(BaseLinker):
 73    """Subclass can link to Companies"""
 74    _link_name = 'company'
 75
 76    def link_company(self, companies):
 77        self._add_link('company', companies)
 78
 79    def unlink_company(self, companies):
 80        self._delete_link('company', companies)
 81
 82
 83class ContactLinker(BaseLinker):
 84    """Subclass can link to Contacts"""
 85    _link_name = 'contact'
 86
 87    def link_contact(self, contacts):
 88        self._add_link('contact', contacts)
 89
 90    def unlink_contact(self, contacts):
 91        self._delete_link('contact', contacts)
 92
 93
 94class CustomerLinker(BaseLinker):
 95    """Subclass can link to Customers"""
 96    _link_name = 'customer'
 97
 98    def link_customer(self, customers):
 99        self._add_link('customer', customers)
100
101    def unlink_customer(self, customers):
102        self._delete_link('customer', customers)
103
104
105class DepartmentLinker(BaseLinker):
106    """Subclass can link to Departments"""
107    _link_name = 'department'
108
109    def link_department(self, departments):
110        self._add_link('department', departments)
111
112    def unlink_department(self, departments):
113        self._delete_link('department', departments)
114
115
116class FileLinker(BaseLinker):
117    """Subclass can link to Files"""
118    _link_name = 'file'
119    _link_key = 'storageFileList'
120
121    def link_file(self, files):
122        self._add_link('file', files)
123
124    def unlink_file(self, files):
125        self._delete_link('file', files)
126
127
128class FolderLinker(BaseLinker):
129    """Subclass can link to Folders"""
130    _link_name = 'folder'
131    _link_key = 'folders'
132
133    def link_folder(self, folders):
134        self._add_link('folder', folders)
135
136    def unlink_folder(self, folders):
137        self._delete_link('folder', folders)
138
139
140class LocationLinker(BaseLinker):
141    """Subclass can link to Locations"""
142    _link_name = 'location'
143
144    def link_location(self, locations):
145        self._add_link('location', locations)
146
147    def unlink_location(self, locations):
148        self._delete_link('location', locations)
149
150
151class PermissionLinker(BaseLinker):
152    """Subclass can link to Permissions"""
153    _link_name = 'permission'
154
155    def link_permission(self, permissions):
156        return self._add_link('permission', permissions)
157
158    def unlink_permission(self, permissions):
159        return self._delete_link('permission', permissions)
160
161
162class ProjectLinker(BaseLinker):
163    """Subclass can link to Projects"""
164    _link_name = 'project'
165
166    def link_project(self, projects):
167        self._add_link('project', projects)
168
169    def unlink_project(self, projects):
170        self._delete_link('project', projects)
171
172
173class RebateLinker(BaseLinker):
174    """Subclass can link to Rebates"""
175    _link_name = 'rebate'
176
177    def link_rebate(self, rebates):
178        self._add_link('rebate', rebates)
179
180    def unlink_rebate(self, rebates):
181        self._delete_link('rebate', rebates)
182
183
184class ResourceLinker(BaseLinker):
185    """Subclass can link to Resources"""
186    _link_name = 'resource'
187    _link_data_name = 'resourceLink'
188
189    def link_resource(self, resources):
190        self._add_link('resource', resources)
191
192    def relink_resource(self, resources):
193        self._update_link('resource', resources)
194
195    def unlink_resource(self, resources):
196        self._delete_link('resource', resources)
197
198
199class SkillLinker(BaseLinker):
200    """Subclass can link to Skills"""
201    _link_name = 'skill'
202    _link_data_name = 'skillLink'
203
204    def link_skill(self, skills):
205        self._add_link('skill', skills)
206
207    def relink_skill(self, skills):
208        self._update_link('skill', skills)
209
210    def unlink_skill(self, skills):
211        self._delete_link('skill', skills)
212
213
214class StaffLinker(BaseLinker):
215    """Subclass can link to Staff"""
216    _link_name = 'staff'
217    _link_data_name = 'resourceLink'
218
219    def link_staff(self, staffs):
220        self._add_link('staff', staffs)
221
222    def relink_staff(self, staffs):
223        self._update_link('staff', staffs)
224
225    def unlink_staff(self, staffs):
226        self._delete_link('staff', staffs)
227
228
229class StageLinker(BaseLinker):
230    """Subclass can link to Stages"""
231    _link_name = 'stage'
232    _link_key = 'stage'
233    _link_type = dict
234
235    def link_stage(self, stages):
236        self._add_link('stage', stages)
237
238    def unlink_stage(self, stages):
239        self._delete_link('stage', stages)
240
241
242class StageListLinker(BaseLinker):
243    """Subclass can link to Stage List"""
244    _link_name = 'stage_list'
245    _link_key = 'stageList'
246    _link_entity = 'Stage'
247
248    def link_stage_list(self, stages):
249        if not isinstance(stages, list):
250            raise api.UsageException('Stage list link must be a list')
251        self._add_link('stage_list', stages)
252
253    def unlink_stage_list(self, stages):
254        if not isinstance(stages, list):
255            raise api.UsageException('Stage list unlink must be a list')
256        stages = [{'uuId': s['uuId']} for s in stages]
257        self._delete_link('stage_list', stages)
258
259
260class UserLinker(BaseLinker):
261    _link_name = 'user'
262
263    def link_user(self, users):
264        self._add_link('user', users)
265
266    def unlink_user(self, users):
267        self._delete_link('user', users)
268
269
270class TaskLinker(BaseLinker):
271    _link_name = 'task'
272
273    def link_task(self, tasks):
274        self._add_link('task', tasks)
275
276    def unlink_task(self, tasks):
277        self._delete_link('task', tasks)
278
279
280class TaskTemplateLinker(BaseLinker):
281    _link_name = 'task_template'
282    _link_entity = 'TaskTemplate'
283
284    def link_task_template(self, task_templates):
285        self._add_link('task_template', task_templates)
286
287    def unlink_task_template(self, task_templates):
288        self._delete_link('task_template', task_templates)
289
290class TagLinker(BaseLinker):
291    _link_name = 'tag'
292
293    def link_tag(self, tags):
294        self._add_link('tag', tags)
295
296    def unlink_tag(self, tags):
297        self._delete_link('tag', tags)
298
299class NoteLinker(BaseLinker):
300    _link_name = 'note'
301
302class CalendarLinker(BaseLinker):
303    _link_name = 'calendar'
304
305# Projects have a list of tasks that we can fetch using the links=
306# method, but they have no linker methods available.
307class TaskInProjectLinker(BaseLinker):
308    _link_name = "task"
class BaseLinker:
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
BaseLinker()
class AccessPolicyLinker(BaseLinker):
60class AccessPolicyLinker(BaseLinker):
61    """Subclass can link to Access Policies"""
62    _link_name = 'access_policy'
63    _link_key = 'accessPolicyList'
64    _link_entity = 'AccessPolicy'
65
66    def link_access_policy(self, access_policies):
67        self._add_link('access_policy', access_policies)
68
69    def unlink_access_policy(self, access_policies):
70        self._delete_link('access_policy', access_policies)

Subclass can link to Access Policies

AccessPolicyLinker()
class CompanyLinker(BaseLinker):
73class CompanyLinker(BaseLinker):
74    """Subclass can link to Companies"""
75    _link_name = 'company'
76
77    def link_company(self, companies):
78        self._add_link('company', companies)
79
80    def unlink_company(self, companies):
81        self._delete_link('company', companies)

Subclass can link to Companies

CompanyLinker()
class ContactLinker(BaseLinker):
84class ContactLinker(BaseLinker):
85    """Subclass can link to Contacts"""
86    _link_name = 'contact'
87
88    def link_contact(self, contacts):
89        self._add_link('contact', contacts)
90
91    def unlink_contact(self, contacts):
92        self._delete_link('contact', contacts)

Subclass can link to Contacts

ContactLinker()
class CustomerLinker(BaseLinker):
 95class CustomerLinker(BaseLinker):
 96    """Subclass can link to Customers"""
 97    _link_name = 'customer'
 98
 99    def link_customer(self, customers):
100        self._add_link('customer', customers)
101
102    def unlink_customer(self, customers):
103        self._delete_link('customer', customers)

Subclass can link to Customers

CustomerLinker()
class DepartmentLinker(BaseLinker):
106class DepartmentLinker(BaseLinker):
107    """Subclass can link to Departments"""
108    _link_name = 'department'
109
110    def link_department(self, departments):
111        self._add_link('department', departments)
112
113    def unlink_department(self, departments):
114        self._delete_link('department', departments)

Subclass can link to Departments

DepartmentLinker()
class FileLinker(BaseLinker):
117class FileLinker(BaseLinker):
118    """Subclass can link to Files"""
119    _link_name = 'file'
120    _link_key = 'storageFileList'
121
122    def link_file(self, files):
123        self._add_link('file', files)
124
125    def unlink_file(self, files):
126        self._delete_link('file', files)

Subclass can link to Files

FileLinker()
class FolderLinker(BaseLinker):
129class FolderLinker(BaseLinker):
130    """Subclass can link to Folders"""
131    _link_name = 'folder'
132    _link_key = 'folders'
133
134    def link_folder(self, folders):
135        self._add_link('folder', folders)
136
137    def unlink_folder(self, folders):
138        self._delete_link('folder', folders)

Subclass can link to Folders

FolderLinker()
class LocationLinker(BaseLinker):
141class LocationLinker(BaseLinker):
142    """Subclass can link to Locations"""
143    _link_name = 'location'
144
145    def link_location(self, locations):
146        self._add_link('location', locations)
147
148    def unlink_location(self, locations):
149        self._delete_link('location', locations)

Subclass can link to Locations

LocationLinker()
class PermissionLinker(BaseLinker):
152class PermissionLinker(BaseLinker):
153    """Subclass can link to Permissions"""
154    _link_name = 'permission'
155
156    def link_permission(self, permissions):
157        return self._add_link('permission', permissions)
158
159    def unlink_permission(self, permissions):
160        return self._delete_link('permission', permissions)

Subclass can link to Permissions

PermissionLinker()
class ProjectLinker(BaseLinker):
163class ProjectLinker(BaseLinker):
164    """Subclass can link to Projects"""
165    _link_name = 'project'
166
167    def link_project(self, projects):
168        self._add_link('project', projects)
169
170    def unlink_project(self, projects):
171        self._delete_link('project', projects)

Subclass can link to Projects

ProjectLinker()
class RebateLinker(BaseLinker):
174class RebateLinker(BaseLinker):
175    """Subclass can link to Rebates"""
176    _link_name = 'rebate'
177
178    def link_rebate(self, rebates):
179        self._add_link('rebate', rebates)
180
181    def unlink_rebate(self, rebates):
182        self._delete_link('rebate', rebates)

Subclass can link to Rebates

RebateLinker()
class ResourceLinker(BaseLinker):
185class ResourceLinker(BaseLinker):
186    """Subclass can link to Resources"""
187    _link_name = 'resource'
188    _link_data_name = 'resourceLink'
189
190    def link_resource(self, resources):
191        self._add_link('resource', resources)
192
193    def relink_resource(self, resources):
194        self._update_link('resource', resources)
195
196    def unlink_resource(self, resources):
197        self._delete_link('resource', resources)

Subclass can link to Resources

ResourceLinker()
class SkillLinker(BaseLinker):
200class SkillLinker(BaseLinker):
201    """Subclass can link to Skills"""
202    _link_name = 'skill'
203    _link_data_name = 'skillLink'
204
205    def link_skill(self, skills):
206        self._add_link('skill', skills)
207
208    def relink_skill(self, skills):
209        self._update_link('skill', skills)
210
211    def unlink_skill(self, skills):
212        self._delete_link('skill', skills)

Subclass can link to Skills

SkillLinker()
class StaffLinker(BaseLinker):
215class StaffLinker(BaseLinker):
216    """Subclass can link to Staff"""
217    _link_name = 'staff'
218    _link_data_name = 'resourceLink'
219
220    def link_staff(self, staffs):
221        self._add_link('staff', staffs)
222
223    def relink_staff(self, staffs):
224        self._update_link('staff', staffs)
225
226    def unlink_staff(self, staffs):
227        self._delete_link('staff', staffs)

Subclass can link to Staff

StaffLinker()
class StageLinker(BaseLinker):
230class StageLinker(BaseLinker):
231    """Subclass can link to Stages"""
232    _link_name = 'stage'
233    _link_key = 'stage'
234    _link_type = dict
235
236    def link_stage(self, stages):
237        self._add_link('stage', stages)
238
239    def unlink_stage(self, stages):
240        self._delete_link('stage', stages)

Subclass can link to Stages

StageLinker()
class StageListLinker(BaseLinker):
243class StageListLinker(BaseLinker):
244    """Subclass can link to Stage List"""
245    _link_name = 'stage_list'
246    _link_key = 'stageList'
247    _link_entity = 'Stage'
248
249    def link_stage_list(self, stages):
250        if not isinstance(stages, list):
251            raise api.UsageException('Stage list link must be a list')
252        self._add_link('stage_list', stages)
253
254    def unlink_stage_list(self, stages):
255        if not isinstance(stages, list):
256            raise api.UsageException('Stage list unlink must be a list')
257        stages = [{'uuId': s['uuId']} for s in stages]
258        self._delete_link('stage_list', stages)

Subclass can link to Stage List

StageListLinker()
class UserLinker(BaseLinker):
261class UserLinker(BaseLinker):
262    _link_name = 'user'
263
264    def link_user(self, users):
265        self._add_link('user', users)
266
267    def unlink_user(self, users):
268        self._delete_link('user', users)
UserLinker()
class TaskLinker(BaseLinker):
271class TaskLinker(BaseLinker):
272    _link_name = 'task'
273
274    def link_task(self, tasks):
275        self._add_link('task', tasks)
276
277    def unlink_task(self, tasks):
278        self._delete_link('task', tasks)
TaskLinker()
class TaskTemplateLinker(BaseLinker):
281class TaskTemplateLinker(BaseLinker):
282    _link_name = 'task_template'
283    _link_entity = 'TaskTemplate'
284
285    def link_task_template(self, task_templates):
286        self._add_link('task_template', task_templates)
287
288    def unlink_task_template(self, task_templates):
289        self._delete_link('task_template', task_templates)
TaskTemplateLinker()
class TagLinker(BaseLinker):
291class TagLinker(BaseLinker):
292    _link_name = 'tag'
293
294    def link_tag(self, tags):
295        self._add_link('tag', tags)
296
297    def unlink_tag(self, tags):
298        self._delete_link('tag', tags)
TagLinker()
class NoteLinker(BaseLinker):
300class NoteLinker(BaseLinker):
301    _link_name = 'note'
NoteLinker()
class CalendarLinker(BaseLinker):
303class CalendarLinker(BaseLinker):
304    _link_name = 'calendar'
CalendarLinker()
class TaskInProjectLinker(BaseLinker):
308class TaskInProjectLinker(BaseLinker):
309    _link_name = "task"
TaskInProjectLinker()