diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16d70f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Ignore pipenv default cookbook folder +cookbook/ + +# Python dev +.env diff --git a/pivotal-import/.gitignore b/pivotal-import/.gitignore index d8ec4d1..7f44565 100644 --- a/pivotal-import/.gitignore +++ b/pivotal-import/.gitignore @@ -3,3 +3,5 @@ config.json # Python .coverage +.env +__pycache__ diff --git a/pivotal-import/README.md b/pivotal-import/README.md index df778e7..695f783 100644 --- a/pivotal-import/README.md +++ b/pivotal-import/README.md @@ -56,7 +56,7 @@ The following are known limitations: - **No story blockers:** Pivotal story blockers (the relationships between stories) are not imported. - **Epics are imported as unstarted:** Imported epics are set to an unstarted "Todo" state. - **No redirects:** The URLs in the descriptions and comments of your Pivotal stories/epics are not rewritten to point to imported Shortcut stories/epics; they remain unchanged. -- **No history:** Project history is not imported into Shortcut. +- **No history:** Project history and story history are not included in Pivotal Tracker's CSV export format and therefore cannot be imported into Shortcut. Our intention is to attend to items higher on the list sooner than those lower. diff --git a/pivotal-import/lib.py b/pivotal-import/lib.py index 91db0e3..7887df0 100644 --- a/pivotal-import/lib.py +++ b/pivotal-import/lib.py @@ -22,6 +22,36 @@ # Logging logger = logging.getLogger(__name__) + +# HTTP / Requests Exception Handling +# https://github.com/useshortcut/api-cookbook/issues/83 +# +# When a non 200 status code is received, give the users information about +# the request and failure +# +def requests_error_decorator(func): + def inner_function(*args, **kwargs): + try: + func(*args, **kwargs) + except requests.exceptions.HTTPError as errh: + error_message = f"\nHTTP Error:\n" + error_message += f" Code: {errh.response.status_code}\n" + error_message += f" Url: {errh.request.url}\n" + error_message += f"\nServer Response:\n {errh.response.json()}" + raise requests.exceptions.HTTPError(error_message) from errh + except requests.exceptions.ConnectionError as errc: + print(f"Error Connecting: {errc}") + raise requests.exceptions.HTTPError(error_message) from errc + except requests.exceptions.Timeout as errt: + print(f"Timeout Error: {errt}") + raise requests.exceptions.HTTPError(error_message) from errt + except requests.exceptions.RequestException as err: + print(f"Unknown Http Error: {err}") + raise requests.exceptions.HTTPError(error_message) from err + + return inner_function + + # Rate limiting. See https://developer.shortcut.com/api/rest/v3#Rate-Limiting # The Shortcut API limit is 200 per minute; the 200th request within 60 seconds # will receive an HTTP 429 response. @@ -65,6 +95,7 @@ def print_rate_limiting_explanation(): @rate_decorator(rate_mapping) +@requests_error_decorator def sc_get(path, params={}): """ Make a GET api call. @@ -75,10 +106,12 @@ def sc_get(path, params={}): logger.debug("GET url=%s params=%s headers=%s" % (url, params, headers)) resp = requests.get(url, headers=headers, params=params) resp.raise_for_status() + logger.debug("Server Response: %s" % (resp.content)) return resp.json() @rate_decorator(rate_mapping) +@requests_error_decorator def sc_post(path, data={}): """Make a POST api call. @@ -97,6 +130,7 @@ def sc_post(path, data={}): @rate_decorator(rate_mapping) +@requests_error_decorator def sc_put(path, data={}): """ Make a PUT api call. @@ -112,6 +146,7 @@ def sc_put(path, data={}): @rate_decorator(rate_mapping) +@requests_error_decorator def sc_upload_files(files): """Upload and associate `files` with the story with given `story_id`""" url = f"{api_url_base}/files" @@ -142,6 +177,7 @@ def sc_upload_files(files): @rate_decorator(rate_mapping) +@requests_error_decorator def sc_delete(path): """ Make a DELETE api call.