projectal API documentation

# 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

### 4.0.1

- Minimum Projectal version is now 4.0.0.

### 4.0.0

Version 4.0.0 accompanies the release of Projectal 4.0.

- Added the `Activity` entity, new in Projectal 4.0.

- Added the `Booking` entity, new in Projectal 4.0.

### 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 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

View Source

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