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.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 to projectal.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 the updated list follow the same old vs new 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 for links=[] 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 on Entity 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 (now juniorLevel, 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 to Company.get_primary_company()

  • Department.tree(): now consumes a holder Entity object instead of a uuId.

  • Department.tree(): added generic_staff parameter, new in Projectal 3.0.

  • Don't break on trailing slash in Projectal URL

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