projectal
A python client for the Projectal API.
Getting started
import projectal
import os
# Supply your Projectal server URL and account credentials
projectal.api_base = https://yourcompany.projectal.com
projectal.api_username = os.environ.get('PROJECTAL_USERNAME')
projectal.api_password = os.environ.get('PROJECTAL_PASSWORD')
# Test communication with server
status = projectal.status()
# Test account credentials
projectal.login()
details = projectal.auth_details()
Changelog
3.1.1
- Link requests generated by 'projectal.Entity.create()' and 'projectal.Entity.update()' are now executed in batches. This is enabled by default with the 'batch_linking=True' parameter and can be disabled to execute each link request individually. It is recommended to leave this parameter enabled as this can greatly reduce the number of network requests.
3.1.0
Minimum Projectal version is now 3.1.5.
Added
projectal.Webhook.list_events()
. See API doc for details on how to use.Added
deleted_at
parameter toprojectal.Entity.get()
. This value should be a UTC timestamp from a webhook delete event.Added
projectal.ldap_sync()
to initiate a user sync with the LDAP/AD service configured in the Projectal server settings.Enhanced output of
projectal.Entity.changes()
function when reporting link changes. It no longer dumps the entire before-and-after list with the full content of each linked entity. Now reports three lists:added
,updated
,removed
. Entities within theupdated
list follow the sameold
vsnew
dictionary model for the data attributes within them. E.g:resourceList: [ 'added': [], 'updated': [ {'uuId': '14eb4c31-0f92-49d1-8b4d-507ab939003e', 'resourceLink': {'utilization': {'old': 0.1, 'new': 0.9}}}, ], 'removed': [] ]
This should result in slimmer logs that are much easier to understand as the changes are clearly indicated.
3.0.2
- Added
projectal.Entity.get_link_definitions()
. Exposes entity link definition dictionary. Consumers can inspect which links an Entity knows about and their internal settings. Link definitions that appear here are the links valid forlinks=[]
parameters.
3.0.1
Fixed fetching project with links=['task'] not being available.
Improved Permission.list(). Now returns a dict with the permission name as key with Permission objects as the value (instead of list of uuIds).
Added a way to use the aliasing feature of the API (new in Projectal 3.0). Set
projectal.api_alias = 'uuid'
to the UUID of a User object and all requests made will be done as that user. Restore this value to None to resume normal operation. (Some rules and limitations apply. See API for more details.)Added complete support for the Tags entity (including linkers).
3.0
Version 3.0 accompanies the release of Projectal 3.0.
Breaking changes:
The
links
parameter onEntity
functions now consumes a list of entity names instead of a comma-separated string. For example:# Before: projectal.Staff.get('<uuid>', links='skill,location') # No longer valid # Now: projectal.Staff.get('<uuid>', links=['skill', 'location'])
The
projectal.enums.SkillLevel
enum has had all values renamed to match the new values used in Projectal (Junior, Mid, Senior). This includes the properties on Skill entities indicating work time for auto-scheduling (nowjuniorLevel
,midLevel
,seniorLevel
).
Other changes:
Working with entity links has changed in this release. The previous methods are still available and continue to work as before, but there is no need to interact with the
projectal.linkers
methods yourself anymore.You can now modify the list of links within an entity and save the entity directly. The library will automatically determine how the links have been modified and issue the correct linker methods on your behalf. E.g., you can now do:
staff = projectal.Staff.get('<uuid>', links=['skill']) staff['firstName'] = "New name" # Field update staff['skillList'] = [skill1, skill2, skill3] # Link update staff.save() # Both changes are saved task = projectal.Task.get('<uuid>', links=['stage']) task['stage'] = stage1 # Uses a single object instead of list task.save()
See
examples/linking.py
for a more complete demonstration of linking capabilities and limitations.Linkers (
projectal.linkers
) can now be given a list of Entities (of one type) to link/unlink/relink in bulk. E.g:staff.unlink_skill(skill1) # Before staff.unlink_skill([skill1, skill2, skill3]) # This works now too
Linkers now strip the payload to only the required fields instead of passing on the entire Entity object. This cuts down on network traffic significantly.
Linkers now also work in reverse. The Projectal server currently only supports linking entities in one direction (e.g., Company to Staff), which often means writing something like:
staff.link_location(location) company.link_staff(staff)
The change in direction is not very intuitive and would require you to constantly verify which direction is the one available to you in the documentation.
Reverse linkers hide this from you and figure out the direction of the relationship for you behind the scenes. So now this is possible, even though the API doesn't strictly support it:
staff.link_location(location) staff.link_company(company)
Caveat: the documentation for Staff will not list Company links. You will still have to look up the Company documentation for the link description.
Requesting entity links with the
links=
parameter will now always ensure the link field (e.g.,taskList
) exists in the result, even if there are no links. The server may not always return a value, but we can use a default value ([] for lists, None for dicts).Added a
Permission
entity to correctly type Permissions in responses.Added a
Tag
entity, new in Projectal 3.0.Added
links
parameter toCompany.get_primary_company()
Department.tree()
: now consumes aholder
Entity object instead of a uuId.Department.tree()
: addedgeneric_staff
parameter, new in Projectal 3.0.Don't break on trailing slash in Projectal URL
When creating tasks, populate the
projectRef
andparent
fields in the returned Task object.Added convenience functions for matching on fields where you only want one result (e.g match_one()) which return the first match found.
Update the entity
history()
method for Projectal 3.0. Some new parameters allow you to restrict the history to a particular range or to get only the changes for a webhook timestamp.Entity objects can call
.history()
on themselves.The library now keeps a reference to the User account that is currently logged in and using the API:
projectal.api_auth_details
.
Known issues:
- You cannot save changes to Notes or Calendars via their holding entity. You
must save the changes on the Note or Calendar directly. To illustrate:
This will be resolved in a future release.staff = projectal.Staff.get(<uuid>, links=['calendar']) calendar = staff['calendarList'][0] calendar['name'] = 'Calendar 2' # Cannot do this - will not pick up the changes staff.save() # You must do this for now calendar.save()
When creating Notes, the
created
andmodified
values may differ by 1ms in the object you have a reference to compared to what is actually stored in the database.Duration calculation is not precise yet (mentioned in 2.1.0)
2.1.0
Breaking changes:
- Getting location calendar is now done on an instance instead of class. So
projectal.Location.calendar(uuid)
is now simplylocation.calendar()
- The
CompanyType.Master
enum has been replaced withCompanyType.Primary
. This was a leftover reference to the Master Company which was renamed in Projectal several versions ago.
Other changes:
- Date conversion functions return None when given None or empty string
- Added
Task.reset_duration()
as a basic duration calculator for tasks. This is a work-in-progress and will be gradually improved. The duration calculator takes into consideration the location to remove non-work days from the estimate of working duration. It currently does not work for the time component orisWorking=True
exceptions. - Change detection in
Entity.changes()
now excludes cases where the server has no value and the new value is None. Saving this change has no effect and would always detect a change until a non-None value is set, which is noisy and generates more network activity.
2.0.3
- Better support for calendars.
- Distinguish between calendar containers ("Calendar") and the calendar items within them ("CalendarItem").
- Allow CalendarItems to be saved directly. E.G item.save()
- Fix 'holder' parameter in contact/staff/location/task_template not permitting object type. Now consumes uuId or object to match rest of the library.
Entity.changes()
has been extended with anold=True
flag. When this flag is true, the set of changes will now return both the original and the new values. E.g.
task.changes()
# {'name': 'current'}
task.changes(old=True)
# {'name': {'old': 'original', 'new': 'current'}}
- Fixed entity link cache causing errors when deleting a link from an entity which has not been fetched with links (deleting from empty list).
2.0.2
- Fixed updating Webhook entities
2.0.1
- Fixed application ID not being used correctly.
2.0.0
- Version 2.0 accompanies the release of Projectal 2.0. There are no major changes since the previous release.
- Expose
Entity.changes()
function. It returns a list of fields on an entity that have changed since fetching it. These are the changes that will be sent over to the server when an update request is made. - Added missing 'packaging' dependency to requirements.
1.2.0
Breaking changes:
- Renamed
request_timestamp
toresponse_timestamp
to better reflect its purpose. Automatic timestamp conversion into dates (introduced in
1.1.0
) has been reverted. All date fields returned from the server remain as UTC timestamps.The reason is that date fields on tasks contain a time component and converting them into date strings was erasing the time, resulting in a value that does not match the database.
Note: the server supports setting date fields using a date string like
2022-04-05
. You may use this if you prefer but the server will always return a timestamp.Note: we provide utility functions for easily converting dates from/to timestamps expected by the Projectal server. See:
projectal.date_from_timestamp()
,projectal.timestamp_from_date()
, andprojectal.timestamp_from_datetime()
.
Other changes:
- Implement request chunking - for methods that consume a list of entities, we now
automatically batch them up into multiple requests to prevent timeouts on really
large request. Values are configurable through
projectal.chunk_size_read
andprojectal.chunk_size_write
. Default values: Read: 1000 items. Write: 200 items. - Added profile get/set functions on entities for easier use. Now you only need to supply the key and the data. E.g:
key = 'hr_connector'
data = {'staff_source': 'company_z'}
task.profile_set(key, data)
Entity link methods now automatically update the entity's cached list of links. E.g: a task fetched with staff links will have
task['staffList'] = [Staff1,Staff2]
. Before, doing atask.link_staff(staff)
did not modify the list to reflect the addition. Now, it will turn into[Staff1,Staff2,Staff3]
. The same applies for update and delete.This allows you to modify links and continue working with that object without having to fetch it again to obtain the most recent link data. Be aware that if you acquire the object without requesting the link data as well (e.g:
projectal.Task.get(id, links='STAFF')
), these lists will not accurately reflect what's in the database, only the changes made while the object is held.Support new
applicationId
property on login. Set with:projectal.api_application_id
. The application ID is sent back to you in webhooks so you know which application was the source of the event (and you can choose to filter them accordingly).Added
Entity.set_readonly()
to allow setting values on entities that will not be sent over to the server when updating/saving the entity.The main use case for this is to populate cached entities which you have just created with values you already know about. This is mainly a workaround for the limitation of the server not sending the full object back after creating it, resulting in the client needing to fetch the object in full again if it needs some of the fields set by the server after creation.
Additionally, some read-only fields will generate an error on the server if included in the update request. This method lets you set these values on newly created objects without triggering this error.
A common example is setting the
projectRef
of a task you just created.
1.1.1
- Add support for 'profiles' API. Profiles are a type of key-value storage that target any entity. Not currently documented.
- Fix handling error message parsing in ProjectalException for batch create operation
- Add
Task.update_order()
to set task order - Return empty list when GETing empty list instead of failing (no request to server)
- Expose the timestamp returned by requests that modify the database. Use
projectal.request_timestamp
to get the value of the most recent request (None if no timestamp in response)
1.1.0
- Minimum Projectal version is now 1.9.4.
Breaking changes:
- Entity
list()
now returns a list of UUIDs instead of full objects. You may provide anexpand
parameter to restore the previous behavior:Entity.list(expand=True)
. This change is made for performance reasons where you may have thousands of tasks and getting them all may time out. For those cases, we suggest writing a query to filter down to only the tasks and fields you need. Company.get_master_company()
has been renamed toCompany.get_primary_company()
to match the server.- The following date fields are converted into date strings upon fetch:
startTime
,closeTime
,scheduleStart
,scheduleFinish
. These fields are added or updated using date strings (like2022-03-02
), but the server returns timestamps (e.g: 1646006400000) upon fetch, which is confusing. This change ensures they are always date strings for consistency.
Other changes:
- When updating an entity, only the fields that have changed are sent to the server. When updating a list of entities, unmodified entities are not sent to the server at all. This dramatically reduces the payload size and should speed things up.
- When fetching entities, entity links are now typed as well. E.g.
project['rebateList']
contains a list ofRebate
instead ofdict
. - Added
date_from_timestamp()
andtimestamp_from_date()
functions to help with converting to/from dates and Projectal timestamps. - Entity history now uses
desc
by default (index 0 is newest) - Added
Project.tasks()
to list all task UUIDs within a project.
1.0.3
- Fix another case of automatic JWT refresh not working
1.0.2
- Entity instances can
save()
ordelete()
on themselves - Fix broken
dict
methods (get()
andupdate()
) when called from Entity instances - Fix automatic JWT refresh only working in some cases
1.0.1
- Added
list()
function for all entities - Added search functions for all entities (match-, search, query)
- Added
Company.get_master_company()
- Fixed adding template tasks
1""" 2A python client for the [Projectal API](https://projectal.com/docs/latest). 3 4## Getting started 5 6``` 7import projectal 8import os 9 10# Supply your Projectal server URL and account credentials 11projectal.api_base = https://yourcompany.projectal.com 12projectal.api_username = os.environ.get('PROJECTAL_USERNAME') 13projectal.api_password = os.environ.get('PROJECTAL_PASSWORD') 14 15# Test communication with server 16status = projectal.status() 17 18# Test account credentials 19projectal.login() 20details = projectal.auth_details() 21``` 22 23---- 24 25## Changelog 26 27### 3.1.1 28- Link requests generated by 'projectal.Entity.create()' and 'projectal.Entity.update()' are now 29 executed in batches. This is enabled by default with the 'batch_linking=True' parameter and can 30 be disabled to execute each link request individually. It is recommended to leave this parameter 31 enabled as this can greatly reduce the number of network requests. 32 33### 3.1.0 34- Minimum Projectal version is now 3.1.5. 35 36- Added `projectal.Webhook.list_events()`. See API doc for details on how to use. 37 38- Added `deleted_at` parameter to `projectal.Entity.get()`. This value should be a UTC timestamp 39 from a webhook delete event. 40 41- Added `projectal.ldap_sync()` to initiate a user sync with the LDAP/AD service configured in 42 the Projectal server settings. 43 44- Enhanced output of `projectal.Entity.changes()` function when reporting link changes. 45 It no longer dumps the entire before-and-after list with the full content of each linked entity. 46 Now reports three lists: `added`, `updated`, `removed`. Entities within the `updated` list 47 follow the same `old` vs `new` dictionary model for the data attributes within them. E.g: 48 49 ``` 50 resourceList: [ 51 'added': [], 52 'updated': [ 53 {'uuId': '14eb4c31-0f92-49d1-8b4d-507ab939003e', 'resourceLink': {'utilization': {'old': 0.1, 'new': 0.9}}}, 54 ], 55 'removed': [] 56 ] 57 ``` 58 This should result in slimmer logs that are much easier to understand as the changes are 59 clearly indicated. 60 61### 3.0.2 62- Added `projectal.Entity.get_link_definitions()`. Exposes entity link definition dictionary. 63 Consumers can inspect which links an Entity knows about and their internal settings. 64 Link definitions that appear here are the links valid for `links=[]` parameters. 65 66### 3.0.1 67- Fixed fetching project with links=['task'] not being available. 68 69- Improved Permission.list(). Now returns a dict with the permission name as 70 key with Permission objects as the value (instead of list of uuIds). 71 72- Added a way to use the aliasing feature of the API (new in Projectal 3.0). 73Set `projectal.api_alias = 'uuid'` to the UUID of a User object and all 74requests made will be done as that user. Restore this value to None to resume 75normal operation. (Some rules and limitations apply. See API for more details.) 76 77- Added complete support for the Tags entity (including linkers). 78 79### 3.0 80 81Version 3.0 accompanies the release of Projectal 3.0. 82 83**Breaking changes**: 84 85- The `links` parameter on `Entity` functions now consumes a list of entity 86 names instead of a comma-separated string. For example: 87 88 ``` 89 # Before: 90 projectal.Staff.get('<uuid>', links='skill,location') # No longer valid 91 # Now: 92 projectal.Staff.get('<uuid>', links=['skill', 'location']) 93 ``` 94 95- The `projectal.enums.SkillLevel` enum has had all values renamed to match the new values 96 used in Projectal (Junior, Mid, Senior). This includes the properties on 97 Skill entities indicating work time for auto-scheduling (now `juniorLevel`, 98 `midLevel`, `seniorLevel`). 99 100**Other changes**: 101 102- Working with entity links has changed in this release. The previous methods 103 are still available and continue to work as before, but there is no need 104 to interact with the `projectal.linkers` methods yourself anymore. 105 106 You can now modify the list of links within an entity and save the entity 107 directly. The library will automatically determine how the links have been 108 modified and issue the correct linker methods on your behalf. E.g., 109 you can now do: 110 111 ``` 112 staff = projectal.Staff.get('<uuid>', links=['skill']) 113 staff['firstName'] = "New name" # Field update 114 staff['skillList'] = [skill1, skill2, skill3] # Link update 115 staff.save() # Both changes are saved 116 117 task = projectal.Task.get('<uuid>', links=['stage']) 118 task['stage'] = stage1 # Uses a single object instead of list 119 task.save() 120 ``` 121 122 See `examples/linking.py` for a more complete demonstration of linking 123 capabilities and limitations. 124 125- Linkers (`projectal.linkers`) can now be given a list of Entities (of one 126 type) to link/unlink/relink in bulk. E.g: 127 ``` 128 staff.unlink_skill(skill1) # Before 129 staff.unlink_skill([skill1, skill2, skill3]) # This works now too 130 ``` 131 132- Linkers now strip the payload to only the required fields instead of passing 133 on the entire Entity object. This cuts down on network traffic significantly. 134 135- Linkers now also work in reverse. The Projectal server currently only supports 136 linking entities in one direction (e.g., Company to Staff), which often means 137 writing something like: 138 ``` 139 staff.link_location(location) 140 company.link_staff(staff) 141 ``` 142 The change in direction is not very intuitive and would require you to constantly 143 verify which direction is the one available to you in the documentation. 144 145 Reverse linkers hide this from you and figure out the direction of the relationship 146 for you behind the scenes. So now this is possible, even though the API doesn't 147 strictly support it: 148 ``` 149 staff.link_location(location) 150 staff.link_company(company) 151 ``` 152 Caveat: the documentation for Staff will not list Company links. You will still 153 have to look up the Company documentation for the link description. 154 155- Requesting entity links with the `links=` parameter will now always ensure the 156 link field (e.g., `taskList`) exists in the result, even if there are no links. 157 The server may not always return a value, but we can use a default value ([] for 158 lists, None for dicts). 159 160- Added a `Permission` entity to correctly type Permissions in responses. 161 162- Added a `Tag` entity, new in Projectal 3.0. 163 164- Added `links` parameter to `Company.get_primary_company()` 165 166- `Department.tree()`: now consumes a `holder` Entity object instead 167 of a uuId. 168 169- `Department.tree()`: added `generic_staff` parameter, new in 170 Projectal 3.0. 171 172- Don't break on trailing slash in Projectal URL 173 174- When creating tasks, populate the `projectRef` and `parent` fields in the 175 returned Task object. 176 177- Added convenience functions for matching on fields where you only want 178 one result (e.g match_one()) which return the first match found. 179 180- Update the entity `history()` method for Projectal 3.0. Some new parameters 181 allow you to restrict the history to a particular range or to get only the 182 changes for a webhook timestamp. 183 184- Entity objects can call `.history()` on themselves. 185 186- The library now keeps a reference to the User account that is currently logged 187 in and using the API: `projectal.api_auth_details`. 188 189**Known issues**: 190- You cannot save changes to Notes or Calendars via their holding entity. You 191 must save the changes on the Note or Calendar directly. To illustrate: 192 ``` 193 staff = projectal.Staff.get(<uuid>, links=['calendar']) 194 calendar = staff['calendarList'][0] 195 calendar['name'] = 'Calendar 2' 196 197 # Cannot do this - will not pick up the changes 198 staff.save() 199 200 # You must do this for now 201 calendar.save() 202 ``` 203 This will be resolved in a future release. 204 205- When creating Notes, the `created` and `modified` values may differ by 206 1ms in the object you have a reference to compared to what is actually 207 stored in the database. 208 209- Duration calculation is not precise yet (mentioned in 2.1.0) 210 211### 2.1.0 212**Breaking changes**: 213- Getting location calendar is now done on an instance instead of class. So 214 `projectal.Location.calendar(uuid)` is now simply `location.calendar()` 215- The `CompanyType.Master` enum has been replaced with `CompanyType.Primary`. 216 This was a leftover reference to the Master Company which was renamed in 217 Projectal several versions ago. 218 219**Other changes**: 220- Date conversion functions return None when given None or empty string 221- Added `Task.reset_duration()` as a basic duration calculator for tasks. 222 This is a work-in-progress and will be gradually improved. The duration 223 calculator takes into consideration the location to remove non-work 224 days from the estimate of working duration. It currently does not work 225 for the time component or `isWorking=True` exceptions. 226- Change detection in `Entity.changes()` now excludes cases where the 227 server has no value and the new value is None. Saving this change has 228 no effect and would always detect a change until a non-None value is 229 set, which is noisy and generates more network activity. 230 231### 2.0.3 232- Better support for calendars. 233 - Distinguish between calendar containers ("Calendar") and the 234 calendar items within them ("CalendarItem"). 235 - Allow CalendarItems to be saved directly. E.G item.save() 236- Fix 'holder' parameter in contact/staff/location/task_template not 237 permitting object type. Now consumes uuId or object to match rest of 238 the library. 239- `Entity.changes()` has been extended with an `old=True` flag. When 240 this flag is true, the set of changes will now return both the original 241 and the new values. E.g. 242``` 243task.changes() 244# {'name': 'current'} 245task.changes(old=True) 246# {'name': {'old': 'original', 'new': 'current'}} 247``` 248- Fixed entity link cache causing errors when deleting a link from an entity 249 which has not been fetched with links (deleting from empty list). 250 251### 2.0.2 252- Fixed updating Webhook entities 253 254### 2.0.1 255- Fixed application ID not being used correctly. 256 257### 2.0.0 258- Version 2.0 accompanies the release of Projectal 2.0. There are no major changes 259 since the previous release. 260- Expose `Entity.changes()` function. It returns a list of fields on an entity that 261 have changed since fetching it. These are the changes that will be sent over to the 262 server when an update request is made. 263- Added missing 'packaging' dependency to requirements. 264 265### 1.2.0 266 267**Breaking changes**: 268 269- Renamed `request_timestamp` to `response_timestamp` to better reflect its purpose. 270- Automatic timestamp conversion into dates (introduced in `1.1.0`) has been reverted. 271 All date fields returned from the server remain as UTC timestamps. 272 273 The reason is that date fields on tasks contain a time component and converting them 274 into date strings was erasing the time, resulting in a value that does not match 275 the database. 276 277 Note: the server supports setting date fields using a date string like `2022-04-05`. 278 You may use this if you prefer but the server will always return a timestamp. 279 280 Note: we provide utility functions for easily converting dates from/to 281 timestamps expected by the Projectal server. See: 282 `projectal.date_from_timestamp()`,`projectal.timestamp_from_date()`, and 283 `projectal.timestamp_from_datetime()`. 284 285**Other changes**: 286- Implement request chunking - for methods that consume a list of entities, we now 287 automatically batch them up into multiple requests to prevent timeouts on really 288 large request. Values are configurable through 289 `projectal.chunk_size_read` and `projectal.chunk_size_write`. 290 Default values: Read: 1000 items. Write: 200 items. 291- Added profile get/set functions on entities for easier use. Now you only need to supply 292 the key and the data. E.g: 293 294``` 295key = 'hr_connector' 296data = {'staff_source': 'company_z'} 297task.profile_set(key, data) 298``` 299 300- Entity link methods now automatically update the entity's cached list of links. E.g: 301 a task fetched with staff links will have `task['staffList'] = [Staff1,Staff2]`. 302 Before, doing a `task.link_staff(staff)` did not modify the list to reflect the 303 addition. Now, it will turn into `[Staff1,Staff2,Staff3]`. The same applies for update 304 and delete. 305 306 This allows you to modify links and continue working with that object without having 307 to fetch it again to obtain the most recent link data. Be aware that if you acquire 308 the object without requesting the link data as well 309 (e.g: `projectal.Task.get(id, links='STAFF')`), 310 these lists will not accurately reflect what's in the database, only the changes made 311 while the object is held. 312 313- Support new `applicationId` property on login. Set with: `projectal.api_application_id`. 314 The application ID is sent back to you in webhooks so you know which application was 315 the source of the event (and you can choose to filter them accordingly). 316- Added `Entity.set_readonly()` to allow setting values on entities that will not 317 be sent over to the server when updating/saving the entity. 318 319 The main use case for this is to populate cached entities which you have just created 320 with values you already know about. This is mainly a workaround for the limitation of 321 the server not sending the full object back after creating it, resulting in the client 322 needing to fetch the object in full again if it needs some of the fields set by the 323 server after creation. 324 325 Additionally, some read-only fields will generate an error on the server if 326 included in the update request. This method lets you set these values on newly 327 created objects without triggering this error. 328 329 A common example is setting the `projectRef` of a task you just created. 330 331 332### 1.1.1 333- Add support for 'profiles' API. Profiles are a type of key-value storage that target 334 any entity. Not currently documented. 335- Fix handling error message parsing in ProjectalException for batch create operation 336- Add `Task.update_order()` to set task order 337- Return empty list when GETing empty list instead of failing (no request to server) 338- Expose the timestamp returned by requests that modify the database. Use 339 `projectal.request_timestamp` to get the value of the most recent request (None 340 if no timestamp in response) 341 342### 1.1.0 343- Minimum Projectal version is now 1.9.4. 344 345**Breaking changes**: 346- Entity `list()` now returns a list of UUIDs instead of full objects. You may provide 347 an `expand` parameter to restore the previous behavior: `Entity.list(expand=True)`. 348 This change is made for performance reasons where you may have thousands of tasks 349 and getting them all may time out. For those cases, we suggest writing a query to filter 350 down to only the tasks and fields you need. 351- `Company.get_master_company()` has been renamed to `Company.get_primary_company()` 352 to match the server. 353- The following date fields are converted into date strings upon fetch: 354 `startTime`, `closeTime`, `scheduleStart`, `scheduleFinish`. 355 These fields are added or updated using date strings (like `2022-03-02`), but the 356 server returns timestamps (e.g: 1646006400000) upon fetch, which is confusing. This 357 change ensures they are always date strings for consistency. 358 359**Other changes**: 360- When updating an entity, only the fields that have changed are sent to the server. When 361 updating a list of entities, unmodified entities are not sent to the server at all. This 362 dramatically reduces the payload size and should speed things up. 363- When fetching entities, entity links are now typed as well. E.g. `project['rebateList']` 364 contains a list of `Rebate` instead of `dict`. 365- Added `date_from_timestamp()` and `timestamp_from_date()` functions to help with 366 converting to/from dates and Projectal timestamps. 367- Entity history now uses `desc` by default (index 0 is newest) 368- Added `Project.tasks()` to list all task UUIDs within a project. 369 370### 1.0.3 371- Fix another case of automatic JWT refresh not working 372 373### 1.0.2 374- Entity instances can `save()` or `delete()` on themselves 375- Fix broken `dict` methods (`get()` and `update()`) when called from Entity instances 376- Fix automatic JWT refresh only working in some cases 377 378### 1.0.1 379- Added `list()` function for all entities 380- Added search functions for all entities (match-, search, query) 381- Added `Company.get_master_company()` 382- Fixed adding template tasks 383 384""" 385import logging 386import os 387 388from projectal.entities import * 389from .api import * 390from . import profile 391 392api_base = os.getenv('PROJECTAL_URL') 393api_username = os.getenv('PROJECTAL_USERNAME') 394api_password = os.getenv('PROJECTAL_PASSWORD') 395api_application_id = None 396api_auth_details = None 397api_alias = None 398cookies = None 399chunk_size_read = 1000 400chunk_size_write = 200 401 402# Records the timestamp generated by the last request (database 403# event time). These are reported on add or updates; if there is 404# no timestamp in the response, this is set to None. 405response_timestamp = None 406 407 408# The minimum version number of the Projectal instance that this 409# API client targets. Lower versions are not supported and will 410# raise an exception. 411MIN_PROJECTAL_VERSION = "3.1.5" 412 413__verify = True 414 415logging.getLogger('projectal-api-client').addHandler(logging.NullHandler())