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