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"
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)