Skip to content

Commit

Permalink
Version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
rossgrambo-zz committed Aug 5, 2019
2 parents 26571ff + da981e7 commit 1852d0f
Show file tree
Hide file tree
Showing 34 changed files with 1,091 additions and 230 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Note: if you're writing a non-browser-based application (e.x. a command line too
Usage
-----

The client's methods are divided into several resources: `attachments`, `events`, `projects`, `stories`, `tags`, `tasks`, `teams`, `users`, and `workspaces`.
The client's methods are divided into several resources: `attachments`, `events`, `jobs`, `portfolios`, `portfolio_memberships`, `projects`, `project_memberships`, `stories`, `tags`, `tasks`, `teams`, `users`, `user_task_lists`, and `workspaces`.

Methods that return a single object return that object directly:

Expand Down Expand Up @@ -122,6 +122,25 @@ Events:
* `poll_interval` (default: 5): polling interval for getting new events via `events.get_next` and `events.get_iterator`
* `sync`: sync token returned by previous calls to `events.get` (in `response['sync']`)

### Asana Change Warnings

You will receive warning logs if performing requests that may be affected by a deprecation. The warning contains a link that explains the deprecation.

If you receive one of these warnings, you should:
* Read about the deprecation.
* Resolve sections of your code that would be affected by the deprecation.
* Add the deprecation flag to your "asana-enable" header.

You can place it on the client for all requests, or place it on a single request.

client.headers={'asana-enable': 'string_ids'}
or
me = client.users.me(headers={'asana-enable': 'string_ids'})

If you would rather suppress these warnings, you can set

client.LOG_ASANA_CHANGE_WARNINGS = false;

Collections
-----------

Expand Down
55 changes: 55 additions & 0 deletions asana/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import platform
import time
import string
import warnings

import requests

Expand Down Expand Up @@ -51,6 +52,8 @@ class Client(object):
'timeout'}
API_OPTIONS = {'pretty', 'fields', 'expand', 'client_name'}

LOG_ASANA_CHANGE_WARNINGS = True

ALL_OPTIONS = (
CLIENT_OPTIONS | QUERY_OPTIONS | REQUEST_OPTIONS | API_OPTIONS)

Expand Down Expand Up @@ -81,6 +84,7 @@ def request(self, method, path, **options):
try:
response = getattr(self.session, method)(
url, auth=self.auth, **request_options)
self._log_asana_change_header(request_options['headers'], response.headers)
if response.status_code in STATUS_MAP:
raise STATUS_MAP[response.status_code](response)
elif 500 <= response.status_code < 600:
Expand All @@ -98,6 +102,57 @@ def request(self, method, path, **options):
else:
raise e

def _log_asana_change_header(self, req_headers, res_headers):
if self.LOG_ASANA_CHANGE_WARNINGS:
change_header_key = None

for key in res_headers:
if key.lower() == 'asana-change':
change_header_key = key

if change_header_key is not None:
accounted_for_flags = []

# Grab the request's asana-enable flags
for reqHeader in req_headers:
if reqHeader.lower() == 'asana-enable':
for flag in req_headers[reqHeader].split(','):
accounted_for_flags.append(flag)
elif reqHeader.lower() == 'asana-disable':
for flag in req_headers[reqHeader].split(','):
accounted_for_flags.append(flag)

changes = res_headers[change_header_key].split(',');

for unsplit_change in changes:
change = unsplit_change.split(';')

name = None
info = None
affected = None

for unsplit_field in change:
field = unsplit_field.split('=')

field[0] = field[0].strip()
if field[0].strip() == 'name':
name = field[1].strip()
elif field[0].strip() == 'info':
info = field[1].strip()
elif field[0].strip() == 'affected':
affected = field[1].strip()

# Only show the error if the flag was not in the request's asana-enable header
if (name not in accounted_for_flags) & (affected == 'true'):
message = 'This request is affected by the "' + name + \
'" deprecation. Please visit this url for more info: ' + info + \
'\n' + 'Adding "' + name + '" to your "Asana-Enable" or ' + \
'"Asana-Disable" header will opt in/out to this deprecation ' + \
'and suppress this warning.'

warnings.warn(message)


def _handle_retryable_error(self, e, retry_count):
"""Sleep based on the type of :class:`RetryableAsanaError`"""
if isinstance(e, error.RateLimitEnforcedError):
Expand Down
4 changes: 4 additions & 0 deletions asana/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
custom_field_settings,
custom_fields,
events,
jobs,
organization_exports,
portfolios,
portfolio_memberships,
project_memberships,
project_statuses,
projects,
Expand All @@ -13,6 +16,7 @@
tasks,
teams,
users,
user_task_lists,
webhooks,
workspaces
)
6 changes: 5 additions & 1 deletion asana/resources/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@

class CustomFields(_CustomFields):
"""Custom Fields resource"""
pass
def add_enum_option(self, custom_field, params={}, **options):
self.create_enum_option(custom_field, params, **options)

def reorder_enum_option(self, custom_field, params={}, **options):
self.insert_enum_option(custom_field, params, **options)
4 changes: 2 additions & 2 deletions asana/resources/gen/attachments.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def find_by_id(self, attachment, params={}, **options):
Parameters
----------
attachment : {Id} Globally unique identifier for the attachment.
attachment : {Gid} Globally unique identifier for the attachment.
[params] : {Object} Parameters for the request
"""
path = "/attachments/%s" % (attachment)
Expand All @@ -24,7 +24,7 @@ def find_by_task(self, task, params={}, **options):
Parameters
----------
task : {Id} Globally unique identifier for the task.
task : {Gid} Globally unique identifier for the task.
[params] : {Object} Parameters for the request
"""
path = "/tasks/%s/attachments" % (task)
Expand Down
26 changes: 19 additions & 7 deletions asana/resources/gen/custom_field_settings.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@

class _CustomFieldSettings:
"""Custom fields are attached to a particular project with the Custom
Field Settings resource. This resource both represents the many-to-many join
of the Custom Field and Project as well as stores information that is relevant to that
particular pairing; for instance, the `is_important` property determines
some possible application-specific handling of that custom field (see below)
"""Custom fields are applied to a particular project or portfolio with the
Custom Field Settings resource. This resource both represents the
many-to-many join of the Custom Field and Project or Portfolio as well as
stores information that is relevant to that particular pairing; for instance,
the `is_important` property determines some possible application-specific
handling of that custom field and parent.
"""

def __init__(self, client=None):
self.client = client

def find_by_project(self, project, params={}, **options):
"""Returns a list of all of the custom fields settings on a project, in compact form. Note that, as in all queries to collections which return compact representation, `opt_fields` and `opt_expand` can be used to include more data than is returned in the compact representation. See the getting started guide on [input/output options](/developers/documentation/getting-started/input-output-options) for more information.
"""Returns a list of all of the custom fields settings on a project.
Parameters
----------
project : {Id} The ID of the project for which to list custom field settings
project : {Gid} The ID of the project for which to list custom field settings
[params] : {Object} Parameters for the request
"""
path = "/projects/%s/custom_field_settings" % (project)
return self.client.get_collection(path, params, **options)

def find_by_portfolio(self, portfolio, params={}, **options):
"""Returns a list of all of the custom fields settings on a portfolio.
Parameters
----------
portfolio : {Gid} The ID of the portfolio for which to list custom field settings
[params] : {Object} Parameters for the request
"""
path = "/portfolios/%s/custom_field_settings" % (portfolio)
return self.client.get_collection(path, params, **options)

46 changes: 30 additions & 16 deletions asana/resources/gen/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ class _CustomFields:
Fields](/developers/documentation/getting-started/custom-fields) developer
documentation for more information about how custom fields relate to various
resources in Asana.
Users in Asana can [lock custom
fields](/guide/help/premium/custom-fields#gl-lock-fields), which will make
them read-only when accessed by other users. Attempting to edit a locked
custom field will return HTTP error code `403 Forbidden`.
"""

def __init__(self, client=None):
Expand All @@ -20,8 +25,9 @@ def create(self, params={}, **options):
Parameters
----------
[data] : {Object} Data for the request
- workspace : {Id} The workspace to create a custom field in.
- type : {String} The type of the custom field.
- workspace : {Gid} The workspace to create a custom field in.
- resource_subtype : {String} The type of the custom field. Must be one of the given values.
- [type] : {String} **Deprecated: New integrations should prefer the `resource_subtype` parameter.**
- name : {String} The name of the custom field.
- [description] : {String} The description of the custom field.
- [precision] : {Integer} The number of decimal places for the numerical values. Required if the custom field is of type 'number'.
Expand All @@ -34,7 +40,7 @@ def find_by_id(self, custom_field, params={}, **options):
Parameters
----------
custom_field : {Id} Globally unique identifier for the custom field.
custom_field : {Gid} Globally unique identifier for the custom field.
[params] : {Object} Parameters for the request
"""
path = "/custom_fields/%s" % (custom_field)
Expand All @@ -45,7 +51,7 @@ def find_by_workspace(self, workspace, params={}, **options):
Parameters
----------
workspace : {Id} The workspace or organization to find custom field definitions in.
workspace : {Gid} The workspace or organization to find custom field definitions in.
[params] : {Object} Parameters for the request
"""
path = "/workspaces/%s/custom_fields" % (workspace)
Expand All @@ -56,15 +62,15 @@ def update(self, custom_field, params={}, **options):
When using this method, it is best to specify only those fields you wish to change, or else you may overwrite changes made by another user since you last retrieved the custom field.
A custom field's `type` cannot be updated.
An enum custom field's `enum_options` cannot be updated with this endpoint. Instead see "Work With Enum Options" for information on how to update `enum_options`.
Locked custom fields can only be updated by the user who locked the field.
Returns the complete updated custom field record.
Parameters
----------
custom_field : {Id} Globally unique identifier for the custom field.
custom_field : {Gid} Globally unique identifier for the custom field.
[data] : {Object} Data for the request
"""
path = "/custom_fields/%s" % (custom_field)
Expand All @@ -73,40 +79,46 @@ def update(self, custom_field, params={}, **options):
def delete(self, custom_field, params={}, **options):
"""A specific, existing custom field can be deleted by making a DELETE request on the URL for that custom field.
Locked custom fields can only be deleted by the user who locked the field.
Returns an empty data record.
Parameters
----------
custom_field : {Id} Globally unique identifier for the custom field.
custom_field : {Gid} Globally unique identifier for the custom field.
"""
path = "/custom_fields/%s" % (custom_field)
return self.client.delete(path, params, **options)

def create_enum_option(self, custom_field, params={}, **options):
"""Creates an enum option and adds it to this custom field's list of enum options. A custom field can have at most 50 enum options (including disabled options). By default new enum options are inserted at the end of a custom field's list.
Locked custom fields can only have enum options added by the user who locked the field.
Returns the full record of the newly created enum option.
Parameters
----------
custom_field : {Id} Globally unique identifier for the custom field.
custom_field : {Gid} Globally unique identifier for the custom field.
[data] : {Object} Data for the request
- name : {String} The name of the enum option.
- [color] : {String} The color of the enum option. Defaults to 'none'.
- [insert_before] : {Id} An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option.
- [insert_after] : {Id} An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option.
- [insert_before] : {Gid} An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option.
- [insert_after] : {Gid} An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option.
"""
path = "/custom_fields/%s/enum_options" % (custom_field)
return self.client.post(path, params, **options)

def update_enum_option(self, enum_option, params={}, **options):
"""Updates an existing enum option. Enum custom fields require at least one enabled enum option.
Locked custom fields can only be updated by the user who locked the field.
Returns the full record of the updated enum option.
Parameters
----------
enum_option : {Id} Globally unique identifier for the enum option.
enum_option : {Gid} Globally unique identifier for the enum option.
[data] : {Object} Data for the request
- name : {String} The name of the enum option.
- [color] : {String} The color of the enum option. Defaults to 'none'.
Expand All @@ -117,16 +129,18 @@ def update_enum_option(self, enum_option, params={}, **options):

def insert_enum_option(self, custom_field, params={}, **options):
"""Moves a particular enum option to be either before or after another specified enum option in the custom field.
Locked custom fields can only be reordered by the user who locked the field.
Parameters
----------
custom_field : {Id} Globally unique identifier for the custom field.
custom_field : {Gid} Globally unique identifier for the custom field.
[data] : {Object} Data for the request
- enum_option : {Id} The ID of the enum option to relocate.
- enum_option : {Gid} The ID of the enum option to relocate.
- name : {String} The name of the enum option.
- [color] : {String} The color of the enum option. Defaults to 'none'.
- [before_enum_option] : {Id} An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option.
- [after_enum_option] : {Id} An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option.
- [before_enum_option] : {Gid} An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option.
- [after_enum_option] : {Gid} An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option.
"""
path = "/custom_fields/%s/enum_options/insert" % (custom_field)
return self.client.post(path, params, **options)
Expand Down
22 changes: 22 additions & 0 deletions asana/resources/gen/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

class _Jobs:
"""A _job_ represents a process that handles asynchronous work.
Jobs are created when an endpoint requests an action that will be handled asynchronously.
Such as project or task duplication.
"""

def __init__(self, client=None):
self.client = client

def find_by_id(self, job, params={}, **options):
"""Returns the complete job record for a single job.
Parameters
----------
job : {Gid} The job to get.
[params] : {Object} Parameters for the request
"""
path = "/jobs/%s" % (job)
return self.client.get(path, params, **options)

4 changes: 2 additions & 2 deletions asana/resources/gen/organization_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def find_by_id(self, organization_export, params={}, **options):
Parameters
----------
organization_export : {Id} Globally unique identifier for the Organization export.
organization_export : {Gid} Globally unique identifier for the Organization export.
[params] : {Object} Parameters for the request
"""
path = "/organization_exports/%s" % (organization_export)
Expand All @@ -36,7 +36,7 @@ def create(self, params={}, **options):
Parameters
----------
[data] : {Object} Data for the request
- organization : {Id} Globally unique identifier for the workspace or organization.
- organization : {Gid} Globally unique identifier for the workspace or organization.
"""
return self.client.post("/organization_exports", params, **options)

Loading

0 comments on commit 1852d0f

Please sign in to comment.