From bea5b47851de79059dc117b5a652f0ae44868e73 Mon Sep 17 00:00:00 2001 From: Jacobi Petrucciani Date: Fri, 1 Jun 2018 12:26:29 -0400 Subject: [PATCH] switching to setup.cfg. reformatting using the black standard. adding requests to the reqs. updating readme a bit. adding get_recently_(modified, created) calls to the deals client --- .flake8 | 4 - .gitignore | 5 + .prospector.yaml | 17 ++- README.md | 37 +++--- hubspot3/base.py | 204 +++++++++++++++++--------------- hubspot3/blog.py | 133 +++++++++++---------- hubspot3/broadcast.py | 153 +++++++++++------------- hubspot3/companies.py | 71 ++++++----- hubspot3/contact_lists.py | 42 +++---- hubspot3/contacts.py | 109 ++++++++--------- hubspot3/deals.py | 166 +++++++++++++++++++------- hubspot3/engagements.py | 58 +++++---- hubspot3/error.py | 24 ++-- hubspot3/forms.py | 15 ++- hubspot3/globals.py | 4 +- hubspot3/keywords.py | 27 +++-- hubspot3/leads.py | 138 ++++++++++----------- hubspot3/mixins/threading.py | 136 +++++++++++---------- hubspot3/owners.py | 23 ++-- hubspot3/prospects.py | 45 +++---- hubspot3/settings.py | 37 +++--- hubspot3/test/helper.py | 34 +++--- hubspot3/test/logger.py | 14 ++- hubspot3/test/test_base.py | 55 +++++---- hubspot3/test/test_broadcast.py | 46 ++++--- hubspot3/test/test_error.py | 35 +++--- hubspot3/test/test_keywords.py | 103 ++++++++-------- hubspot3/test/test_leads.py | 29 ++--- hubspot3/test/test_settings.py | 41 ++++--- hubspot3/utils.py | 34 +++--- requirements.txt | 3 +- setup.cfg | 9 ++ setup.py | 34 +++--- 33 files changed, 989 insertions(+), 896 deletions(-) delete mode 100644 .flake8 create mode 100644 setup.cfg diff --git a/.flake8 b/.flake8 deleted file mode 100644 index bbbb0da..0000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -ignore = N802 -max-line-length = 100 -max-complexity = 10 diff --git a/.gitignore b/.gitignore index 958d7b2..71a9478 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ .prospector +*.pyc +.coverage +.mypy_cache +.pytest_cache + # Created by https://www.gitignore.io/api/python diff --git a/.prospector.yaml b/.prospector.yaml index 7731ff8..185d398 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -5,27 +5,22 @@ test-warnings: false ignore-patterns: - (^|/)\..+ - - .*/env\.py - .*\.html - - .*\.env\.py - - .*templates/ pylint: disable: - - fixme - bad-continuation - - too-many-arguments - - dangerous-default-value - - import-self - broad-except - - no-self-use - - unused-argument + - import-error + - import-self - logging-format-interpolation - missing-docstring + - no-self-use + - unused-argument - wrong-import-order options: - max-arguments: 10 + max-args: 10 max-locals: 100 max-returns: 6 max-branches: 50 @@ -44,6 +39,8 @@ mccabe: pep8: disable: - N802 + - N807 + - W503 options: max-line-length: 100 single-line-if-stmt: n diff --git a/README.md b/README.md index 5fcc7d2..3ad8653 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # hubspot3 [![PyPI version](https://badge.fury.io/py/hubspot3.svg)](https://badge.fury.io/py/hubspot3) -[![Code Health](https://landscape.io/github/jpetrucciani/hubspot3/master/landscape.svg?style=flat)](https://landscape.io/github/jpetrucciani/hubspot3/master) - +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) A python wrapper around HubSpot\'s APIs, _for python 3_. @@ -10,11 +9,22 @@ Built initially around hapipy, but heavily modified. ## Quick start +### Installation + +```bash +# on ubuntu you may need this apt package: +sudo apt-get install libcurl4-openssl-dev + +# install hubspot3 +pip install hubspot3 +``` + ### Basic Usage ```python from hubspot3.companies import CompaniesClient -API_KEY = 'your-api-key' + +API_KEY = "your-api-key" client = CompaniesClient(api_key=API_KEY) @@ -28,13 +38,13 @@ for company in client.get_all(): import json from hubspot3.deals import DealsClient -deal_id = '12345' -API_KEY = 'your_api_key' +deal_id = "12345" +API_KEY = "your_api_key" deals_client = DealsClient(api_key=API_KEY) params = { - 'includePropertyVersions': 'true' + "includePropertyVersions": "true" } # Note values are camelCase as they appear in the Hubspot Documentation! deal_data = deals_client.get(deal_id, params=params) @@ -47,20 +57,20 @@ Be aware that this uses the HubSpot API directly, so you are subject to all of t https://developers.hubspot.com/apps/api_guidelines at the time of writing, HubSpot has the following limits in place for API requests: + - 10 requests per second - 40,000 requests per day. This daily limit resets at midnight based on the time zone setting of the HubSpot account - ## Extending the BaseClient - thanks [@Guysoft](https://github.com/guysoft)! -Some of the APIs are not yet complete! If you\'d like to use an API that isn\'t yet in this repo, you can extend the BaseClient class! +Some of the APIs are not yet complete! If you\'d like to use an API that isn\'t yet in this repo, you can extend the BaseClient class! ```python import json from hubspot3.base import BaseClient -PIPELINES_API_VERSION = '1' +PIPELINES_API_VERSION = "1" class PipelineClient(BaseClient): @@ -74,23 +84,20 @@ class PipelineClient(BaseClient): def get_pipelines(self, **options): params = {} - return self._call('pipelines', method='GET', params=params) + return self._call("pipelines", method="GET", params=params) def _get_path(self, subpath): - return 'deals/v{}/{}'.format( - self.options.get('version') or PIPELINES_API_VERSION, - subpath + return "deals/v{}/{}".format( + self.options.get("version") or PIPELINES_API_VERSION, subpath ) if __name__ == "__main__": - import json API_KEY = "your_api_key" a = PipelineClient(api_key=API_KEY) print(json.dumps(a.get_pipelines())) ``` - ## List of available clients ```yaml diff --git a/hubspot3/base.py b/hubspot3/base.py index fe0b00b..7c3b97f 100644 --- a/hubspot3/base.py +++ b/hubspot3/base.py @@ -11,19 +11,15 @@ import time import traceback import zlib -from hubspot3 import ( - utils -) -from hubspot3.utils import ( - force_utf8 -) +from hubspot3 import utils +from hubspot3.utils import force_utf8 from hubspot3.error import ( HubspotError, HubspotBadRequest, HubspotNotFound, HubspotTimeout, HubspotServerError, - HubspotUnauthorized + HubspotUnauthorized, ) @@ -32,6 +28,7 @@ class BaseClient(object): """Base abstract object for interacting with the HubSpot APIs""" + # Controls how long we sleep for during retries, overridden by unittests # so tests run faster sleep_multiplier = 1 @@ -40,7 +37,7 @@ def __init__( self, api_key=None, timeout=10, - mixins=[], + mixins=None, access_token=None, refresh_token=None, client_id=None, @@ -48,87 +45,92 @@ def __init__( ): super(BaseClient, self).__init__() # reverse so that the first one in the list because the first parent + if not mixins: + mixins = [] mixins.reverse() for mixin_class in mixins: if mixin_class not in self.__class__.__bases__: self.__class__.__bases__ = (mixin_class,) + self.__class__.__bases__ - self.api_key = api_key or extra_options.get('api_key') - self.access_token = access_token or extra_options.get('access_token') - self.refresh_token = refresh_token or extra_options.get('refresh_token') - self.client_id = client_id or extra_options.get('client_id') - self.log = utils.get_log('hubspot3') + self.api_key = api_key or extra_options.get("api_key") + self.access_token = access_token or extra_options.get("access_token") + self.refresh_token = refresh_token or extra_options.get("refresh_token") + self.client_id = client_id or extra_options.get("client_id") + self.log = utils.get_log("hubspot3") if self.api_key and self.access_token: - raise Exception('Cannot use both api_key and access_token.') + raise Exception("Cannot use both api_key and access_token.") if not (self.api_key or self.access_token or self.refresh_token): - raise Exception('Missing required credentials.') - self.options = { - 'api_base': 'api.hubapi.com', - 'debug': False - } + raise Exception("Missing required credentials.") + self.options = {"api_base": "api.hubapi.com", "debug": False} if not _PYTHON25: - self.options['timeout'] = timeout + self.options["timeout"] = timeout self.options.update(extra_options) self._prepare_connection_type() def _prepare_connection_type(self): connection_types = { - 'http': http.client.HTTPConnection, - 'https': http.client.HTTPSConnection + "http": http.client.HTTPConnection, + "https": http.client.HTTPSConnection, } - parts = self.options['api_base'].split('://') - protocol = (parts[0:-1] + ['https'])[0] - self.options['connection_type'] = connection_types[protocol] - self.options['protocol'] = protocol - self.options['api_base'] = parts[-1] + parts = self.options["api_base"].split("://") + protocol = (parts[0:-1] + ["https"])[0] + self.options["connection_type"] = connection_types[protocol] + self.options["protocol"] = protocol + self.options["api_base"] = parts[-1] def _get_path(self, subpath): - raise Exception('Unimplemented get_path for BaseClient subclass!') + raise Exception("Unimplemented get_path for BaseClient subclass!") def _prepare_request_auth(self, subpath, params, data, opts): if self.api_key: - params['hapikey'] = params.get('hapikey') or self.api_key + params["hapikey"] = params.get("hapikey") or self.api_key else: # Be sure that we're consistent about what access_token is being used # If one was provided at instantiation, that is always used. If it was not # but one was provided as part of the method invocation, we persist it - if params.get('access_token') and not self.access_token: - self.access_token = params.get('access_token') - params['access_token'] = self.access_token + if params.get("access_token") and not self.access_token: + self.access_token = params.get("access_token") + params["access_token"] = self.access_token - def _prepare_request(self, subpath, params, data, opts, doseq=False, query=''): + def _prepare_request(self, subpath, params, data, opts, doseq=False, query=""): params = params or {} self._prepare_request_auth(subpath, params, data, opts) - if opts.get('hub_id') or opts.get('portal_id'): - params['portalId'] = opts.get('hub_id') or opts.get('portal_id') + if opts.get("hub_id") or opts.get("portal_id"): + params["portalId"] = opts.get("hub_id") or opts.get("portal_id") if query is None: - query = '' - if query and query.startswith('?'): + query = "" + if query and query.startswith("?"): query = query[1:] - if query and not query.startswith('&'): - query = '&' + query - url = opts.get('url') or '/{}?{}{}'.format( - self._get_path(subpath), - urllib.parse.urlencode(params, doseq), - query + if query and not query.startswith("&"): + query = "&" + query + url = opts.get("url") or "/{}?{}{}".format( + self._get_path(subpath), urllib.parse.urlencode(params, doseq), query + ) + headers = opts.get("headers") or {} + headers.update( + { + "Accept-Encoding": "gzip", + "Content-Type": opts.get("content_type") or "application/json", + } ) - headers = opts.get('headers') or {} - headers.update({ - 'Accept-Encoding': 'gzip', - 'Content-Type': opts.get('content_type') or 'application/json' - }) - if data and headers['Content-Type'] == 'application/json': + if data and headers["Content-Type"] == "application/json": data = json.dumps(data) return url, headers, data def _create_request(self, conn, method, url, headers, data): conn.request(method, url, data, headers) - params = {'method': method, 'url': url, 'data': data, 'headers': headers, 'host': conn.host} + params = { + "method": method, + "url": url, + "data": data, + "headers": headers, + "host": conn.host, + } if not _PYTHON25: - params['timeout'] = conn.timeout + params["timeout"] = conn.timeout return params def _gunzip_body(self, body): @@ -145,13 +147,15 @@ def _execute_request_raw(self, conn, request): except Exception: raise HubspotTimeout(None, request, traceback.format_exc()) - encoding = [i[1] for i in result.getheaders() if i[0] == 'content-encoding'] + encoding = [i[1] for i in result.getheaders() if i[0] == "content-encoding"] possibly_encoded = result.read() try: possibly_encoded = zlib.decompress(possibly_encoded, 16 + zlib.MAX_WBITS) except Exception: pass - result.body = self._process_body(possibly_encoded, len(encoding) and encoding[0] == 'gzip') + result.body = self._process_body( + possibly_encoded, len(encoding) and encoding[0] == "gzip" + ) conn.close() if result.status in (404, 410): @@ -188,48 +192,39 @@ def _call_raw( self, subpath, params=None, - method='GET', + method="GET", data=None, doseq=False, - query='', + query="", retried=False, **options ): opts = self.options.copy() opts.update(options) - debug = opts.get('debug') + debug = opts.get("debug") url, headers, data = self._prepare_request( - subpath, - params, - data, - opts, - doseq=doseq, - query=query + subpath, params, data, opts, doseq=doseq, query=query ) if debug: print( json.dumps( - { - 'url': url, - 'headers': headers, - 'data': data - }, + {"url": url, "headers": headers, "data": data}, sort_keys=True, - indent=2 + indent=2, ) ) kwargs = {} if not _PYTHON25: - kwargs['timeout'] = opts['timeout'] + kwargs["timeout"] = opts["timeout"] - num_retries = opts.get('number_retries', 0) + num_retries = opts.get("number_retries", 0) # Never retry a POST, PUT, or DELETE unless explicitly told to - if method != 'GET' and not opts.get('retry_on_post'): + if method != "GET" and not opts.get("retry_on_post"): num_retries = 0 if num_retries > 6: num_retries = 6 @@ -243,24 +238,34 @@ def _call_raw( break try: try_count += 1 - connection = opts['connection_type'](opts['api_base'], **kwargs) - request_info = self._create_request(connection, method, url, headers, data) + connection = opts["connection_type"](opts["api_base"], **kwargs) + request_info = self._create_request( + connection, method, url, headers, data + ) result = self._execute_request_raw(connection, request_info) break - except HubspotUnauthorized as exception: - self.log.warning('401 Unauthorized response to API request.') - if self.access_token and self.refresh_token and self.client_id and not retried: - self.log.info('Refreshing access token') + except HubspotUnauthorized: + self.log.warning("401 Unauthorized response to API request.") + if ( + self.access_token + and self.refresh_token + and self.client_id + and not retried + ): + self.log.info("Refreshing access token") try: token_response = utils.refresh_access_token( - self.refresh_token, - self.client_id + self.refresh_token, self.client_id ) decoded = json.loads(token_response) - self.access_token = decoded['access_token'] - self.log.info('Retrying with new token {}'.format(self.access_token)) + self.access_token = decoded["access_token"] + self.log.info( + "Retrying with new token {}".format(self.access_token) + ) except Exception as exception: - self.log.error('Unable to refresh access_token: {}'.format(exception)) + self.log.error( + "Unable to refresh access_token: {}".format(exception) + ) raise return self._call_raw( subpath, @@ -273,35 +278,42 @@ def _call_raw( **options ) else: - if self.access_token and self.refresh_token and self.client_id and retried: + if ( + self.access_token + and self.refresh_token + and self.client_id + and retried + ): self.log.error( - 'Refreshed token, but request still was not authorized. ' - ' You may need to grant additional permissions.' + "Refreshed token, but request still was not authorized. " + " You may need to grant additional permissions." ) elif self.access_token and not self.refresh_token: self.log.error( - 'In order to enable automated refreshing of your access ' - 'token, please provide a refresh token as well.' + "In order to enable automated refreshing of your access " + "token, please provide a refresh token as well." ) elif self.access_token and not self.client_id: self.log.error( - 'In order to enable automated refreshing of your access ' - 'token, please provide a client_id in addition to a refresh token.' + "In order to enable automated refreshing of your access " + "token, please provide a client_id in addition to a refresh token." ) raise except HubspotError as exception: if try_count > num_retries: - logging.warning('Too many retries for {}'.format(url)) + logging.warning("Too many retries for {}".format(url)) raise # Don't retry errors from 300 to 499 if ( - exception.result and - exception.result.status >= 300 and - exception.result.status < 500 + exception.result + and exception.result.status >= 300 + and exception.result.status < 500 ): raise self._prepare_request_retry(method, url, headers, data) - self.log.warning('HubspotError {} calling {}, retrying'.format(exception, url)) + self.log.warning( + "HubspotError {} calling {}, retrying".format(exception, url) + ) # exponential back off - wait 0 seconds, 1 second, 3 seconds, 7 seconds, 15 seconds, etc time.sleep((pow(2, try_count - 1) - 1) * self.sleep_multiplier) return result @@ -310,10 +322,10 @@ def _call( self, subpath, params=None, - method='GET', + method="GET", data=None, doseq=False, - query='', + query="", **options ): result = self._call_raw( diff --git a/hubspot3/blog.py b/hubspot3/blog.py index d279be0..5189e36 100644 --- a/hubspot3/blog.py +++ b/hubspot3/blog.py @@ -2,52 +2,55 @@ hubspot blog api client """ import json -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient -BLOG_API_VERSION = '1' +BLOG_API_VERSION = "1" class BlogClient(BaseClient): """ provides a client for accessing hubspot blog info """ + def _get_path(self, subpath): - return 'blog/v{}/{}'.format(BLOG_API_VERSION, subpath) + return "blog/v{}/{}".format(BLOG_API_VERSION, subpath) def get_blogs(self, **options): - return self._call('list.json', **options) + return self._call("list.json", **options) def get_blog_info(self, blog_guid, **options): return self._call(blog_guid, **options) def get_posts(self, blog_guid, **options): - return self._call('{}/posts.json'.format(blog_guid), **options) + return self._call("{}/posts.json".format(blog_guid), **options) def get_draft_posts(self, blog_guid, **options): - return self._call('{}/posts.json'.format(blog_guid), params={'draft': 'true'}, **options) + return self._call( + "{}/posts.json".format(blog_guid), params={"draft": "true"}, **options + ) def get_published_posts(self, blog_guid, **options): - params = dict(draft='false') + params = dict(draft="false") params.update(options) - return self._call('{}/posts.json'.format(blog_guid), params=params) + return self._call("{}/posts.json".format(blog_guid), params=params) # Spelled wrong but left for compat def get_pulished_posts(self, blog_guid, **options): - return self._call('{}/posts.json'.format(blog_guid), params={'draft': 'false'}, **options) + return self._call( + "{}/posts.json".format(blog_guid), params={"draft": "false"}, **options + ) def get_blog_comments(self, blog_guid, **options): - return self._call('{}/comments.json'.format(blog_guid), **options) + return self._call("{}/comments.json".format(blog_guid), **options) def get_post(self, post_guid, **options): - return self._call('posts/{}.json'.format(post_guid), **options) + return self._call("posts/{}.json".format(post_guid), **options) def get_post_comments(self, post_guid, **options): - return self._call('posts/{}/comments.json'.format(post_guid), **options) + return self._call("posts/{}/comments.json".format(post_guid), **options) def get_comment(self, comment_guid, **options): - return self._call('comments/{}.json'.format(comment_guid), **options) + return self._call("comments/{}.json".format(comment_guid), **options) def create_post( self, @@ -62,20 +65,23 @@ def create_post( meta_keyword, **options ): - post = json.dumps(dict( - title=title, - authorDisplayName=author_name, - authorEmail=author_email, - summary=summary, - body=content, - tags=tags, - metaDescription=meta_desc, - metaKeywords=meta_keyword)) + post = json.dumps( + dict( + title=title, + authorDisplayName=author_name, + authorEmail=author_email, + summary=summary, + body=content, + tags=tags, + metaDescription=meta_desc, + metaKeywords=meta_keyword, + ) + ) raw_response = self._call( - '{}/posts.json'.format(blog_guid), + "{}/posts.json".format(blog_guid), data=post, - method='POST', - content_type='application/json', + method="POST", + content_type="application/json", raw_output=True, **options ) @@ -94,59 +100,66 @@ def update_post( ): tags = tags or [] update_param_translation = dict( - itle='title', - summary='summary', - content='body', - meta_desc='metaDescription', - meta_keyword='metaKeywords', - tags='tags' + itle="title", + summary="summary", + content="body", + meta_desc="metaDescription", + meta_keyword="metaKeywords", + tags="tags", ) post_dict = dict( - [(k, locals()[p]) for p, k in update_param_translation.items() if locals().get(p)]) + [ + (k, locals()[p]) + for p, k in update_param_translation.items() + if locals().get(p) + ] + ) post = json.dumps(post_dict) raw_response = self._call( - 'posts/{}.json'.format(post_guid), + "posts/{}.json".format(post_guid), data=post, - method='PUT', - content_type='application/json', + method="PUT", + content_type="application/json", raw_output=True, **options ) return raw_response def publish_post( - self, - post_guid, - should_notify, - publish_time=None, - is_draft='false', - **options + self, post_guid, should_notify, publish_time=None, is_draft="false", **options ): - post = json.dumps(dict( - published=publish_time, - draft=is_draft, - sendNotifications=should_notify)) + post = json.dumps( + dict( + published=publish_time, draft=is_draft, sendNotifications=should_notify + ) + ) raw_response = self._call( - 'posts/{}.json'.format(post_guid), + "posts/{}.json".format(post_guid), data=post, - method='PUT', - content_type='application/json', + method="PUT", + content_type="application/json", raw_output=True, **options ) return raw_response - def create_comment(self, post_guid, author_name, author_email, author_uri, content, **options): - post = json.dumps(dict( - anonyName=author_name, - anonyEmail=author_email, - anonyUrl=author_uri, - comment=content)) + def create_comment( + self, post_guid, author_name, author_email, author_uri, content, **options + ): + post = json.dumps( + dict( + anonyName=author_name, + anonyEmail=author_email, + anonyUrl=author_uri, + comment=content, + ) + ) raw_response = self._call( - 'posts/{}/comments.json'.format(post_guid), + "posts/{}/comments.json".format(post_guid), data=post, - method='POST', - content_type='application/json', - raw_output=True, **options + method="POST", + content_type="application/json", + raw_output=True, + **options ) return raw_response diff --git a/hubspot3/broadcast.py b/hubspot3/broadcast.py index e3d5512..a7b332f 100644 --- a/hubspot3/broadcast.py +++ b/hubspot3/broadcast.py @@ -1,44 +1,46 @@ """ hubspot broadcast api """ -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient -HUBSPOT_BROADCAST_API_VERSION = '1' +HUBSPOT_BROADCAST_API_VERSION = "1" class BaseSocialObject(object): """base social object""" + def _camel_case_to_underscores(self, text): result = [] pos = 0 while pos < len(text): if text[pos].isupper(): if ( - pos - 1 > 0 and text[pos - 1].islower() or - pos - 1 > 0 and pos + 1 < len(text) and text[pos + 1].islower() + pos - 1 > 0 + and text[pos - 1].islower() + or pos - 1 > 0 + and pos + 1 < len(text) + and text[pos + 1].islower() ): - result.append('_{}'.format(text[pos].lower())) + result.append("_{}".format(text[pos].lower())) else: result.append(text[pos].lower()) else: result.append(text[pos]) pos += 1 - return ''.join(result) + return "".join(result) def _underscores_to_camel_case(self, text): result = [] pos = 0 while pos < len(text): - if text[pos] == '_' and pos + 1 < len(text): - result.append('{}'.format(text[pos + 1].upper())) + if text[pos] == "_" and pos + 1 < len(text): + result.append("{}".format(text[pos + 1].upper())) pos += 1 else: result.append(text[pos]) pos += 1 - return ''.join(result) + return "".join(result) def to_dict(self): dict_self = {} @@ -55,39 +57,40 @@ def from_dict(self, data): class Broadcast(BaseSocialObject): """Defines a social media broadcast message for the broadcast api""" + # Constants for remote content type - COS_LP = 'coslp' - COS_BLOG = 'cosblog' - LEGACY_LP = 'cmslp' - LEGACY_BLOG = 'cmsblog' + COS_LP = "coslp" + COS_BLOG = "cosblog" + LEGACY_LP = "cmslp" + LEGACY_BLOG = "cmsblog" def __init__(self, broadcast_data): self.data_parse(broadcast_data) def accepted_fields(self): return [ - 'broadcastGuid', - 'campaignGuid', - 'channel', - 'channelGuid', - 'clicks', - 'clientTag', - 'content', - 'createdAt', - 'createdBy', - 'finishedAt', - 'groupGuid', - 'interactions', - 'interactionCounts', - 'linkGuid', - 'message', - 'messageUrl', - 'portalId', - 'remoteContentId', - 'remoteContentType', - 'status', - 'triggerAt', - 'updatedBy' + "broadcastGuid", + "campaignGuid", + "channel", + "channelGuid", + "clicks", + "clientTag", + "content", + "createdAt", + "createdBy", + "finishedAt", + "groupGuid", + "interactions", + "interactionCounts", + "linkGuid", + "message", + "messageUrl", + "portalId", + "remoteContentId", + "remoteContentType", + "status", + "triggerAt", + "updatedBy", ] def data_parse(self, broadcast_data): @@ -96,19 +99,20 @@ def data_parse(self, broadcast_data): class Channel(BaseSocialObject): """Defines the social media channel for the broadcast api""" + def __init__(self, channel_data): self.data_parse(channel_data) def accepted_fields(self): return [ - 'channelGuid', - 'accountGuid', - 'account', - 'type', - 'name', - 'dataMap', - 'createdAt', - 'settings' + "channelGuid", + "accountGuid", + "account", + "type", + "name", + "dataMap", + "createdAt", + "settings", ] def data_parse(self, channel_data): @@ -117,8 +121,9 @@ def data_parse(self, channel_data): class BroadcastClient(BaseClient): """Broadcast API to manage messages published to social networks""" - def _get_path(self, method): - return 'broadcast/v{}/{}'.format(HUBSPOT_BROADCAST_API_VERSION, method) + + def _get_path(self, subpath): + return "broadcast/v{}/{}".format(HUBSPOT_BROADCAST_API_VERSION, subpath) def get_broadcast(self, broadcast_guid, **kwargs): """ @@ -126,19 +131,14 @@ def get_broadcast(self, broadcast_guid, **kwargs): """ params = kwargs broadcast = self._call( - 'broadcasts/{}'.format(broadcast_guid), + "broadcasts/{}".format(broadcast_guid), params=params, - content_type='application/json' + content_type="application/json", ) return Broadcast(broadcast) def get_broadcasts( - self, - broadcast_type='', - page=None, - remote_content_id=None, - limit=None, - **kwargs + self, broadcast_type="", page=None, remote_content_id=None, limit=None, **kwargs ): """ Get all broadcasts, with optional paging and limits. @@ -147,16 +147,14 @@ def get_broadcasts( if remote_content_id: return self.get_broadcasts_by_remote(remote_content_id) - params = {'type': broadcast_type} + params = {"type": broadcast_type} if page: - params['page'] = page + params["page"] = page params.update(kwargs) result = self._call( - 'broadcasts', - params=params, - content_type='application/json' + "broadcasts", params=params, content_type="application/json" ) broadcasts = [Broadcast(b) for b in result] @@ -167,36 +165,29 @@ def get_broadcasts( def create_broadcast(self, broadcast): if not isinstance(broadcast, dict): return self._call( - 'broadcasts', + "broadcasts", data=broadcast.to_dict(), - method='POST', - content_type='application/json' + method="POST", + content_type="application/json", ) return self._call( - 'broadcasts', - data=broadcast, - method='POST', - content_type='application/json' + "broadcasts", data=broadcast, method="POST", content_type="application/json" ) def cancel_broadcast(self, broadcast_guid): """ Cancel a broadcast specified by guid """ - subpath = 'broadcasts/{}/update'.format(broadcast_guid) - broadcast = {'status': 'CANCELED'} + subpath = "broadcasts/{}/update".format(broadcast_guid) + broadcast = {"status": "CANCELED"} bcast_dict = self._call( - subpath, - method='POST', - data=broadcast, - content_type='application/json' + subpath, method="POST", data=broadcast, content_type="application/json" ) return bcast_dict def get_channel(self, channel_guid): channel = self._call( - 'channels/{}'.format(channel_guid), - content_type='application/json' + "channels/{}".format(channel_guid), content_type="application/json" ) return Channel(channel) @@ -213,18 +204,16 @@ def get_channels(self, current=True, publish_only=False, settings=False): """ if publish_only: if current: - endpoint = 'channels/setting/publish/current' + endpoint = "channels/setting/publish/current" else: - endpoint = 'channels/setting/publish' + endpoint = "channels/setting/publish" else: if current: - endpoint = 'channels/current' + endpoint = "channels/current" else: - endpoint = 'channels' + endpoint = "channels" result = self._call( - endpoint, - content_type='application/json', - params=dict(settings=settings) + endpoint, content_type="application/json", params=dict(settings=settings) ) return [Channel(c) for c in result] diff --git a/hubspot3/companies.py b/hubspot3/companies.py index 28da039..9ea9778 100644 --- a/hubspot3/companies.py +++ b/hubspot3/companies.py @@ -1,18 +1,12 @@ """ hubspot companies api """ -from hubspot3 import ( - logging_helper -) -from hubspot3.base import ( - BaseClient -) -from hubspot3.utils import ( - prettify -) +from hubspot3 import logging_helper +from hubspot3.base import BaseClient +from hubspot3.utils import prettify -COMPANIES_API_VERSION = '2' +COMPANIES_API_VERSION = "2" class CompaniesClient(BaseClient): @@ -23,29 +17,25 @@ class CompaniesClient(BaseClient): def __init__(self, *args, **kwargs): super(CompaniesClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.companies') + self.log = logging_helper.get_log("hapi.companies") def _get_path(self, subpath): - return 'companies/v{}/{}'.format( - self.options.get('version') or COMPANIES_API_VERSION, - subpath + return "companies/v{}/{}".format( + self.options.get("version") or COMPANIES_API_VERSION, subpath ) def create(self, data=None, **options): data = data or {} - return self._call('companies/', data=data, method='POST', **options) + return self._call("companies/", data=data, method="POST", **options) def update(self, key, data=None, **options): data = data or {} return self._call( - 'companies/{}'.format(key), - data=data, - method='PUT', - **options + "companies/{}".format(key), data=data, method="PUT", **options ) def get(self, companyid, **options): - return self._call('companies/{}'.format(companyid), method='GET', **options) + return self._call("companies/{}".format(companyid), method="GET", **options) def get_all(self, **options): finished = False @@ -54,28 +44,33 @@ def get_all(self, **options): querylimit = 250 # Max value according to docs while not finished: batch = self._call( - 'companies/paged', method='GET', doseq=True, + "companies/paged", + method="GET", + doseq=True, params={ - 'limit': querylimit, - 'offset': offset, - 'properties': [ - 'name', - 'description', - 'address', - 'address2', - 'city', - 'state', - 'story', - 'hubspot_owner_id' + "limit": querylimit, + "offset": offset, + "properties": [ + "name", + "description", + "address", + "address2", + "city", + "state", + "story", + "hubspot_owner_id", ], }, **options ) - output.extend([ - prettify(company, id_key='companyId') - for company in batch['companies'] if not company['isDeleted'] - ]) - finished = not batch['has-more'] - offset = batch['offset'] + output.extend( + [ + prettify(company, id_key="companyId") + for company in batch["companies"] + if not company["isDeleted"] + ] + ) + finished = not batch["has-more"] + offset = batch["offset"] return output diff --git a/hubspot3/contact_lists.py b/hubspot3/contact_lists.py index 9fac94d..f1d2258 100644 --- a/hubspot3/contact_lists.py +++ b/hubspot3/contact_lists.py @@ -1,15 +1,11 @@ """ hubspot contact lists api """ -from hubspot3 import ( - logging_helper -) -from hubspot3.base import ( - BaseClient -) +from hubspot3 import logging_helper +from hubspot3.base import BaseClient -CONTACT_LISTS_API_VERSION = '1' +CONTACT_LISTS_API_VERSION = "1" class ContactListsClient(BaseClient): @@ -17,39 +13,43 @@ class ContactListsClient(BaseClient): The hubspot3 Contact Lists client uses the _make_request method to call the API for data. It returns a python object translated from the json return """ + def __init__(self, *args, **kwargs): super(ContactListsClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.contact_lists') + self.log = logging_helper.get_log("hapi.contact_lists") def _get_path(self, subpath): - return 'contacts/v{}/{}'.format( - self.options.get('version') or CONTACT_LISTS_API_VERSION, - subpath + return "contacts/v{}/{}".format( + self.options.get("version") or CONTACT_LISTS_API_VERSION, subpath ) def get_contact_lists(self, **options): """Returns all of the contact lists""" - return self._call('lists', method='GET', **options) + return self._call("lists", method="GET", **options) def add_contact_to_a_list(self, list_id, vids, data=None, **options): """Adds a list of contact vids to the specified list.""" data = data or {} - data['vids'] = vids + data["vids"] = vids return self._call( - 'lists/{list_id}/add'.format(list_id=list_id), + "lists/{list_id}/add".format(list_id=list_id), data=data, - method='POST', + method="POST", **options ) - def create_a_contact_list(self, list_name, portal_id, dynamic=True, data=None, **options): + def create_a_contact_list( + self, list_name, portal_id, dynamic=True, data=None, **options + ): """Creates a contact list with given list_name on the given portal_id.""" data = data or {} - data['name'] = list_name - data['portal_id'] = portal_id - data['dynamic'] = dynamic - return self._call('lists', data=data, method='POST', **options) + data["name"] = list_name + data["portal_id"] = portal_id + data["dynamic"] = dynamic + return self._call("lists", data=data, method="POST", **options) def delete_a_contact_list(self, list_id, **options): """Deletes the contact list by list_id.""" - return self._call('lists/{list_id}'.format(list_id=list_id), method='DELETE', **options) + return self._call( + "lists/{list_id}".format(list_id=list_id), method="DELETE", **options + ) diff --git a/hubspot3/contacts.py b/hubspot3/contacts.py index 9d1b2c4..efc336f 100644 --- a/hubspot3/contacts.py +++ b/hubspot3/contacts.py @@ -1,18 +1,12 @@ """ hubspot contacts api """ -from hubspot3 import ( - logging_helper -) -from hubspot3.base import ( - BaseClient -) -from hubspot3.utils import ( - prettify -) +from hubspot3 import logging_helper +from hubspot3.base import BaseClient +from hubspot3.utils import prettify -CONTACTS_API_VERSION = '1' +CONTACTS_API_VERSION = "1" class ContactsClient(BaseClient): @@ -20,93 +14,91 @@ class ContactsClient(BaseClient): The hubspot3 Contacts client uses the _make_request method to call the API for data. It returns a python object translated from the json return """ + def __init__(self, *args, **kwargs): super(ContactsClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.contacts') + self.log = logging_helper.get_log("hapi.contacts") def _get_path(self, subpath): - return 'contacts/v{}/{}'.format( - CONTACTS_API_VERSION, - subpath - ) + return "contacts/v{}/{}".format(CONTACTS_API_VERSION, subpath) def create_or_update_a_contact(self, email, data=None, **options): """Creates or Updates a client with the supplied data.""" data = data or {} return self._call( - 'contact/createOrUpdate/email/{email}'.format(email=email), - data=data, method='POST', **options + "contact/createOrUpdate/email/{email}".format(email=email), + data=data, + method="POST", + **options ) def get_contact_by_email(self, email, **options): """Gets contact specified by email address.""" return self._call( - 'contact/email/{email}/profile'.format(email=email), - method='GET', - **options + "contact/email/{email}/profile".format(email=email), method="GET", **options ) def get_contact_by_id(self, contact_id, **options): """Gets contact specified by ID""" return self._call( - 'contact/vid/{}/profile'.format(contact_id), - method='GET', - **options + "contact/vid/{}/profile".format(contact_id), method="GET", **options ) def update_a_contact(self, contact_id, data=None, **options): """Updates the contact by contact_id with the given data.""" data = data or {} return self._call( - 'contact/vid/{contact_id}/profile'.format(contact_id=contact_id), - data=data, method='POST', **options + "contact/vid/{contact_id}/profile".format(contact_id=contact_id), + data=data, + method="POST", + **options ) def delete_a_contact(self, contact_id, **options): """Deletes a contact by contact_id.""" return self._call( - 'contact/vid/{contact_id}'.format(contact_id=contact_id), - method='DELETE', + "contact/vid/{contact_id}".format(contact_id=contact_id), + method="DELETE", **options ) def create(self, data=None, **options): data = data or {} - return self._call('contact', data=data, method='POST', **options) + return self._call("contact", data=data, method="POST", **options) + + def update(self, key, data=None, **options): + + if not data: + data = {} - def update(self, key, data={}, **options): return self._call( - 'contact/vid/{}/profile'.format(key), - data=data, - method='POST', - **options + "contact/vid/{}/profile".format(key), data=data, method="POST", **options ) def get_batch(self, ids): batch = self._call( - 'contact/vids/batch', - method='GET', + "contact/vids/batch", + method="GET", doseq=True, params={ - 'vid': ids, - 'property': [ - 'email', - 'firstname', - 'lastname', - 'company', - 'website', - 'phone', - 'address', - 'city', - 'state', - 'zip', - 'associatedcompanyid' + "vid": ids, + "property": [ + "email", + "firstname", + "lastname", + "company", + "website", + "phone", + "address", + "city", + "state", + "zip", + "associatedcompanyid", ], - } + }, ) # It returns a dict with IDs as keys - return [prettify(batch[contact], id_key='vid') - for contact in batch] + return [prettify(batch[contact], id_key="vid") for contact in batch] def get_all(self, **options): # Can't get phone number from a get-all, so we just grab IDs and @@ -117,18 +109,15 @@ def get_all(self, **options): querylimit = 100 # Max value according to docs while not finished: batch = self._call( - 'lists/all/contacts/all', - method='GET', - params={ - 'count': querylimit, - 'vidOffset': offset - }, + "lists/all/contacts/all", + method="GET", + params={"count": querylimit, "vidOffset": offset}, **options ) output.extend( - self.get_batch([contact['vid'] for contact in batch['contacts']]) + self.get_batch([contact["vid"] for contact in batch["contacts"]]) ) - finished = not batch['has-more'] - offset = batch['vid-offset'] + finished = not batch["has-more"] + offset = batch["vid-offset"] return output diff --git a/hubspot3/deals.py b/hubspot3/deals.py index 0d28a8b..5c6957a 100644 --- a/hubspot3/deals.py +++ b/hubspot3/deals.py @@ -1,19 +1,13 @@ """ hubspot deals api """ -from hubspot3 import ( - logging_helper -) -from hubspot3.base import ( - BaseClient -) -from hubspot3.utils import ( - prettify -) +from hubspot3 import logging_helper +from hubspot3.base import BaseClient +from hubspot3.utils import prettify import urllib.parse -DEALS_API_VERSION = '1' +DEALS_API_VERSION = "1" class DealsClient(BaseClient): @@ -21,75 +15,157 @@ class DealsClient(BaseClient): The hubspot3 Deals client uses the _make_request method to call the API for data. It returns a python object translated from the json return """ + def __init__(self, *args, **kwargs): super(DealsClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.deals') + self.log = logging_helper.get_log("hapi.deals") def _get_path(self, subpath): - return 'deals/v{}/{}'.format( - self.options.get('version') or DEALS_API_VERSION, - subpath + return "deals/v{}/{}".format( + self.options.get("version") or DEALS_API_VERSION, subpath ) def get(self, deal_id, **options): - return self._call('deal/{}'.format(deal_id), method='GET', **options) + return self._call("deal/{}".format(deal_id), method="GET", **options) def create(self, data=None, **options): data = data or {} - return self._call('deal/', data=data, method='POST', **options) + return self._call("deal/", data=data, method="POST", **options) def update(self, key, data=None, **options): data = data or {} - return self._call('deal/{}'.format(key), data=data, - method='PUT', **options) + return self._call("deal/{}".format(key), data=data, method="PUT", **options) def associate(self, deal_id, object_type, object_ids, **options): # Encoding the query string here since HubSpot is expecting the "id" parameter to be # repeated for each object ID, which is not a standard practice and won't work otherwise. - object_ids = [('id', object_id) for object_id in object_ids] + object_ids = [("id", object_id) for object_id in object_ids] query = urllib.parse.urlencode(object_ids) return self._call( - 'deal/{}/associations/{}'.format(deal_id, object_type), - method='PUT', + "deal/{}/associations/{}".format(deal_id, object_type), + method="PUT", query=query, **options ) - def get_all(self, limit=None, offset=0, **options): + def get_all(self, offset=0, **options): + """get all deals in the hubspot account""" finished = False output = [] - offset = 0 querylimit = 250 # Max value according to docs while not finished: batch = self._call( - 'deal/paged', - method='GET', + "deal/paged", + method="GET", params={ - 'limit': querylimit, - 'offset': offset, - 'properties': [ - 'associations', - 'dealname', - 'dealstage', - 'pipeline', - 'hubspot_owner_id', - 'description', - 'closedate', - 'amount', - 'dealtype', - 'createdate' + "limit": querylimit, + "offset": offset, + "properties": [ + "associations", + "dealname", + "dealstage", + "pipeline", + "hubspot_owner_id", + "description", + "closedate", + "amount", + "dealtype", + "createdate", ], - 'includeAssociations': True + "includeAssociations": True, }, doseq=True, **options ) - output.extend([ - prettify(deal, id_key='dealId') - for deal in batch['deals'] if not deal['isDeleted'] - ]) - finished = not batch['hasMore'] - offset = batch['offset'] + output.extend( + [ + prettify(deal, id_key="dealId") + for deal in batch["deals"] + if not deal["isDeleted"] + ] + ) + finished = not batch["hasMore"] + offset = batch["offset"] return output + + def get_recently_created( + self, limit=100, offset=0, since=None, include_versions=False, **options + ): + """ + get recently created deals + up to the last 30 days or the 10k most recently created records + + since: must be a UNIX formatted timestamp in milliseconds + """ + finished = False + output = [] + querylimit = 100 # max according to the docs + + while not finished: + params = { + "count": querylimit, + "offset": offset, + "includePropertyVersions": include_versions, + } + if since: + params["since"] = since + batch = self._call( + "deal/recent/created", + method="GET", + params=params, + doseq=True, + **options + ) + output.extend( + [ + prettify(deal, id_key="dealId") + for deal in batch["results"] + if not deal["isDeleted"] + ] + ) + finished = not batch["hasMore"] or len(output) >= limit + offset = batch["offset"] + + return output[:limit] + + def get_recently_modified( + self, limit=100, offset=0, since=None, include_versions=False, **options + ): + """ + get recently modified deals + up to the last 30 days or the 10k most recently modified records + + since: must be a UNIX formatted timestamp in milliseconds + """ + finished = False + output = [] + querylimit = 100 # max according to the docs + + while not finished: + params = { + "count": querylimit, + "offset": offset, + "includePropertyVersions": include_versions, + } + if since: + params["since"] = since + batch = self._call( + "deal/recent/modified", + method="GET", + params=params, + doseq=True, + **options + ) + output.extend( + [ + prettify(deal, id_key="dealId") + for deal in batch["results"] + if not deal["isDeleted"] + ] + ) + finished = not batch["hasMore"] or len(output) >= limit + offset = batch["offset"] + + return output[:limit] diff --git a/hubspot3/engagements.py b/hubspot3/engagements.py index f614271..61c87ac 100644 --- a/hubspot3/engagements.py +++ b/hubspot3/engagements.py @@ -1,15 +1,11 @@ """ hubspot engagements api """ -from hubspot3 import ( - logging_helper -) -from hubspot3.base import ( - BaseClient -) +from hubspot3 import logging_helper +from hubspot3.base import BaseClient -ENGAGEMENTS_API_VERSION = '1' +ENGAGEMENTS_API_VERSION = "1" class EngagementsClient(BaseClient): @@ -17,19 +13,21 @@ class EngagementsClient(BaseClient): The hubspot3 Engagements client uses the _make_request method to call the API for data. It returns a python object translated from the json return """ + def __init__(self, *args, **kwargs): super(EngagementsClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.engagements') + self.log = logging_helper.get_log("hapi.engagements") def _get_path(self, subpath): - return 'engagements/v{}/{}'.format( - self.options.get('version') or ENGAGEMENTS_API_VERSION, - subpath + return "engagements/v{}/{}".format( + self.options.get("version") or ENGAGEMENTS_API_VERSION, subpath ) def get(self, engagement_id, **options): """Get a HubSpot engagement.""" - return self._call('engagements/{}'.format(engagement_id), method='GET', **options) + return self._call( + "engagements/{}".format(engagement_id), method="GET", **options + ) def get_associated(self, object_type, object_id, **options): finished = False @@ -39,29 +37,27 @@ def get_associated(self, object_type, object_id, **options): while not finished: print(offset) batch = self._call( - 'engagements/associated/{}/{}/paged'.format( - object_type, - object_id - ), - method='GET', - params={'limit': querylimit, 'offset': offset}, + "engagements/associated/{}/{}/paged".format(object_type, object_id), + method="GET", + params={"limit": querylimit, "offset": offset}, **options ) - print(len(batch['results'])) - output.extend(batch['results']) - finished = not batch['hasMore'] - offset = batch['offset'] + print(len(batch["results"])) + output.extend(batch["results"]) + finished = not batch["hasMore"] + offset = batch["offset"] return output def create(self, data=None, **options): data = data or {} - return self._call('engagements', data=data, method='POST', **options) + return self._call("engagements", data=data, method="POST", **options) def update(self, key, data=None, **options): data = data or {} - return self._call('engagements/{}'.format(key), data=data, - method='PUT', **options) + return self._call( + "engagements/{}".format(key), data=data, method="PUT", **options + ) def get_all(self, **options): finished = False @@ -70,11 +66,13 @@ def get_all(self, **options): offset = 0 while not finished: batch = self._call( - 'engagements/paged', method='GET', - params={'limit': querylimit, 'offset': offset}, **options + "engagements/paged", + method="GET", + params={"limit": querylimit, "offset": offset}, + **options ) - output.extend(batch['results']) - finished = not batch['hasMore'] - offset = batch['offset'] + output.extend(batch["results"]) + finished = not batch["hasMore"] + offset = batch["offset"] return output diff --git a/hubspot3/error.py b/hubspot3/error.py index 3533418..bdc3e4a 100644 --- a/hubspot3/error.py +++ b/hubspot3/error.py @@ -1,9 +1,7 @@ """ hubspot3 error helpers """ -from hubspot3.utils import ( - force_utf8 -) +from hubspot3.utils import force_utf8 class EmptyResult(object): @@ -11,11 +9,12 @@ class EmptyResult(object): Null Object pattern to prevent Null reference errors when there is no result """ + def __init__(self): self.status = 0 - self.body = '' - self.msg = '' - self.reason = '' + self.body = "" + self.msg = "" + self.reason = "" def __bool__(self): return False @@ -23,6 +22,7 @@ def __bool__(self): class HubspotError(ValueError): """Any problems get thrown as HubspotError exceptions with the relevant info inside""" + as_str_template = """ ---- request ---- {method} {host}{url}, [timeout={timeout}] @@ -57,7 +57,7 @@ def __bool__(self): return False def __init__(self, result, request, err=None): - super(HubspotError, self).__init__(result and result.reason or 'Unknown Reason') + super(HubspotError, self).__init__(result and result.reason or "Unknown Reason") if result is None: self.result = EmptyResult() else: @@ -72,13 +72,13 @@ def __str__(self): def __unicode__(self): params = {} - request_keys = ('method', 'host', 'url', 'data', 'headers', 'timeout', 'body') - result_attrs = ('status', 'reason', 'msg', 'body', 'headers') - params['error'] = self.err + request_keys = ("method", "host", "url", "data", "headers", "timeout", "body") + result_attrs = ("status", "reason", "msg", "body", "headers") + params["error"] = self.err for key in request_keys: params[key] = self.request.get(key) for attr in result_attrs: - params['result_{}'.format(attr)] = getattr(self.result, attr, '') + params["result_{}".format(attr)] = getattr(self.result, attr, "") params = self._dict_vals_to_unicode(params) return self.as_str_template.format(**params) @@ -87,7 +87,7 @@ def _dict_vals_to_unicode(self, data): unicode_data = {} for key, val in list(data.items()): if not val: - unicode_data[key] = '' + unicode_data[key] = "" if isinstance(val, bytes): unicode_data[key] = force_utf8(val) elif isinstance(val, str): diff --git a/hubspot3/forms.py b/hubspot3/forms.py index 52a52de..71adfa8 100644 --- a/hubspot3/forms.py +++ b/hubspot3/forms.py @@ -1,28 +1,27 @@ """ hubspot forms api """ -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient class FormSubmissionClient(BaseClient): """allows acccess to the forms api""" + def __init__(self, *args, **kwargs): super(FormSubmissionClient, self).__init__(*args, **kwargs) - self.options['api_base'] = 'forms.hubspot.com' + self.options["api_base"] = "forms.hubspot.com" def _get_path(self, subpath): - return '/uploads/form/v2/{}'.format(subpath) + return "/uploads/form/v2/{}".format(subpath) def submit_form(self, portal_id, form_guid, data, **options): - subpath = '{}/{}'.format(portal_id, form_guid) - opts = {'content_type': 'application/x-www-form-urlencoded'} + subpath = "{}/{}".format(portal_id, form_guid) + opts = {"content_type": "application/x-www-form-urlencoded"} options.update(opts) return self._call( subpath=None, url=self._get_path(subpath), - method='POST', + method="POST", data=data, **options ) diff --git a/hubspot3/globals.py b/hubspot3/globals.py index 8b2e74a..4b18313 100644 --- a/hubspot3/globals.py +++ b/hubspot3/globals.py @@ -3,7 +3,7 @@ """ -__version__ = '3.1.1' +__version__ = "3.1.2" -BASE_URL = 'https://api.hubapi.com' +BASE_URL = "https://api.hubapi.com" diff --git a/hubspot3/keywords.py b/hubspot3/keywords.py index 041e8ed..c723048 100644 --- a/hubspot3/keywords.py +++ b/hubspot3/keywords.py @@ -1,41 +1,44 @@ """ hubspot keywords api """ -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient -KEYWORDS_API_VERSION = 'v1' +KEYWORDS_API_VERSION = "v1" class KeywordsClient(BaseClient): """allows access to the keywords api""" + def _get_path(self, subpath): - return 'keywords/{}/{}'.format(KEYWORDS_API_VERSION, subpath) + return "keywords/{}/{}".format(KEYWORDS_API_VERSION, subpath) # Contains both list of keywords and metadata def get_keywords_info(self, **options): - return self._call('keywords', **options) + return self._call("keywords", **options) # *Only* returns the list of keywords, does not include additional metadata def get_keywords(self, **options): - return self._call('keywords', **options)['keywords'] + return self._call("keywords", **options)["keywords"] def get_keyword(self, keyword_guid, **options): - return self._call('keywords/{}'.format(keyword_guid, **options)) + return self._call("keywords/{}".format(keyword_guid, **options)) def add_keyword(self, keyword, **options): - return self._call('keywords', data=dict(keyword=str(keyword)), method='PUT', **options) + return self._call( + "keywords", data=dict(keyword=str(keyword)), method="PUT", **options + ) def add_keywords(self, keywords, **options): data = [] for keyword in keywords: - if keyword != '': + if keyword != "": if isinstance(keyword, dict): data.append(keyword) elif isinstance(keyword, str): data.append(dict(keyword=str(keyword))) - return self._call('keywords', data=data, method='PUT', **options)['keywords'] + return self._call("keywords", data=data, method="PUT", **options)["keywords"] def delete_keyword(self, keyword_guid, **options): - return self._call('keywords/{}'.format(keyword_guid), method='DELETE', **options) + return self._call( + "keywords/{}".format(keyword_guid), method="DELETE", **options + ) diff --git a/hubspot3/leads.py b/hubspot3/leads.py index f3e977a..5e40cc4 100644 --- a/hubspot3/leads.py +++ b/hubspot3/leads.py @@ -2,15 +2,11 @@ hubspot leads api """ import time -from hubspot3.base import ( - BaseClient -) -from hubspot3 import ( - logging_helper -) +from hubspot3.base import BaseClient +from hubspot3 import logging_helper -LEADS_API_VERSION = '1' +LEADS_API_VERSION = "1" def list_to_snake_dict(list_): @@ -18,54 +14,59 @@ def list_to_snake_dict(list_): for item in list_: dictionary[item] = item if item.lower() != item: - python_variant = item[0].lower() + ''.join( - [c if c.lower() == c else '_{}'.format(c.lower()) for c in item[1:]] + python_variant = item[0].lower() + "".join( + [c if c.lower() == c else "_{}".format(c.lower()) for c in item[1:]] ) dictionary[python_variant] = item return dictionary SORT_OPTIONS = [ - 'firstName', - 'lastName', - 'email', - 'address', - 'phone', - 'insertedAt', - 'fce.convertDate', - 'lce.convertDate', - 'lastModifiedAt', - 'closedAt'] + "firstName", + "lastName", + "email", + "address", + "phone", + "insertedAt", + "fce.convertDate", + "lce.convertDate", + "lastModifiedAt", + "closedAt", +] SORT_OPTIONS_DICT = list_to_snake_dict(SORT_OPTIONS) TIME_PIVOT_OPTIONS = [ - 'insertedAt', - 'firstConvertedAt', - 'lastConvertedAt', - 'lastModifiedAt', - 'closedAt' + "insertedAt", + "firstConvertedAt", + "lastConvertedAt", + "lastModifiedAt", + "closedAt", ] TIME_PIVOT_OPTIONS_DICT = list_to_snake_dict(TIME_PIVOT_OPTIONS) SEARCH_OPTIONS = [ - 'search', - 'sort', - 'dir', - 'max', - 'offset', - 'startTime', - 'stopTime', - 'timePivot', - 'excludeConversionEvents', - 'emailOptOut', - 'eligibleForEmail', - 'bounced', - 'isNotImported'] + "search", + "sort", + "dir", + "max", + "offset", + "startTime", + "stopTime", + "timePivot", + "excludeConversionEvents", + "emailOptOut", + "eligibleForEmail", + "bounced", + "isNotImported", +] SEARCH_OPTIONS_DICT = list_to_snake_dict(SEARCH_OPTIONS) -BOOLEAN_SEARCH_OPTIONS = set([ - 'excludeConversionEvents', - 'emailOptOut', - 'eligibleForEmail', - 'bounced', - 'isNotImported']) +BOOLEAN_SEARCH_OPTIONS = set( + [ + "excludeConversionEvents", + "emailOptOut", + "eligibleForEmail", + "bounced", + "isNotImported", + ] +) MAX_BATCH = 100 @@ -75,9 +76,10 @@ class LeadsClient(BaseClient): The hubspot3 Leads client uses the _make_request method to call the API for data. It returns a python object translated from the json return """ + def __init__(self, *args, **kwargs): super(LeadsClient, self).__init__(*args, **kwargs) - self.log = logging_helper.get_log('hapi.leads') + self.log = logging_helper.get_log("hapi.leads") def camelcase_search_options(self, options): """change all underscored variants back to what the API is expecting""" @@ -85,9 +87,9 @@ def camelcase_search_options(self, options): for key in options: value = options[key] new_key = SEARCH_OPTIONS_DICT.get(key, key) - if new_key == 'sort': + if new_key == "sort": value = SORT_OPTIONS_DICT.get(value, value) - elif new_key == 'timePivot': + elif new_key == "timePivot": value = TIME_PIVOT_OPTIONS_DICT.get(value, value) elif new_key in BOOLEAN_SEARCH_OPTIONS: value = str(value).lower() @@ -95,7 +97,9 @@ def camelcase_search_options(self, options): return new_options def _get_path(self, subpath): - return 'leads/v{}/{}'.format(self.options.get('version') or LEADS_API_VERSION, subpath) + return "leads/v{}/{}".format( + self.options.get("version") or LEADS_API_VERSION, subpath + ) def get_lead(self, guid, **options): return self.get_leads(guid, **options)[0] @@ -106,29 +110,29 @@ def get_leads(self, *guids, **options): options = self.camelcase_search_options(options.copy()) params = {} for i, guid in enumerate(guids): - params['guids[{}]'.format(i)] = guid + params["guids[{}]".format(i)] = guid for k in list(options.keys()): if k in SEARCH_OPTIONS: params[k] = options[k] del options[k] - leads = self._call('list/', params, **options) - self.log.info('retrieved {} leads through API ( {}options={} )'.format( - len(leads), - guids and 'guids={}, '.format(guids or ''), - original_options) + leads = self._call("list/", params, **options) + self.log.info( + "retrieved {} leads through API ( {}options={} )".format( + len(leads), guids and "guids={}, ".format(guids or ""), original_options + ) ) return leads def retrieve_lead(self, *guid, **options): - cur_guid = guid or '' + cur_guid = guid or "" params = {} for key in options: params[key] = options[key] # Set guid to -1 as default for not finding a user - lead = {'guid': '-1'} + lead = {"guid": "-1"} # wrap lead call so that it doesn't error out when not finding a lead try: - lead = self._call('lead/{}'.format(cur_guid), params, **options) + lead = self._call("lead/{}".format(cur_guid), params, **options) except Exception: # no lead here pass @@ -136,27 +140,27 @@ def retrieve_lead(self, *guid, **options): def update_lead(self, guid, update_data=None, **options): update_data = update_data or {} - update_data['guid'] = guid - return self._call('lead/{}/'.format(guid), data=update_data, method='PUT', **options) + update_data["guid"] = guid + return self._call( + "lead/{}/".format(guid), data=update_data, method="PUT", **options + ) def get_webhook(self, **options): # WTF are these 2 methods for? - return self._call('callback-url', **options) + return self._call("callback-url", **options) def register_webhook(self, url, **options): return self._call( - 'callback-url', - params={'url': url}, - data={'url': url}, - method='POST', + "callback-url", + params={"url": url}, + data={"url": url}, + method="POST", **options ) def close_lead(self, guid, close_time=None, **options): return self.update_lead( - guid, - {'closedAt': close_time or int(time.time() * 1000)}, - **options + guid, {"closedAt": close_time or int(time.time() * 1000)}, **options ) def open_lead(self, guid, **options): - self.update_lead(guid, {'closedAt': ''}, **options) + self.update_lead(guid, {"closedAt": ""}, **options) diff --git a/hubspot3/mixins/threading.py b/hubspot3/mixins/threading.py index 6dcd36c..bf5dd1f 100644 --- a/hubspot3/mixins/threading.py +++ b/hubspot3/mixins/threading.py @@ -6,27 +6,28 @@ class Hubspot3ThreadedError(ValueError): - """""" + """general exception for the multithreading mixin""" + def __init__(self, curl): super(Hubspot3ThreadedError, self).__init__(curl.body.getvalue()) - self.c = curl - self.response_body = self.c.body.getvalue() - self.response_headers = self.c.response_headers.getvalue() + self.curl_call = curl + self.response_body = self.curl_call.body.getvalue() + self.response_headers = self.curl_call.response_headers.getvalue() def __str__(self): return ( - '\n---- request ----\n{} {}{} [timeout={}]\n\n---- body ----\n{}\n\n---- headers' - ' ----\n{}\n\n---- result ----\n{}\n\n---- body ----\n{}\n\n---- headers ' - '----\n{}'.format( - getattr(self.c, 'method', ''), - self.c.host, - self.c.path, - self.c.timeout, - self.c.data, - self.c.headers, - self.c.status, + "\n---- request ----\n{} {}{} [timeout={}]\n\n---- body ----\n{}\n\n---- headers" + " ----\n{}\n\n---- result ----\n{}\n\n---- body ----\n{}\n\n---- headers " + "----\n{}".format( + getattr(self.curl_call, "method", ""), + self.curl_call.host, + self.curl_call.path, + self.curl_call.timeout, + self.curl_call.data, + self.curl_call.headers, + self.curl_call.status, self.response_body, - self.response_headers + self.response_headers, ) ) @@ -57,7 +58,10 @@ class PyCurlMixin(object): in the order they were called. Dicts have keys: data, code, and (if something went wrong) exception. """ - def _call(self, subpath, params=None, method='GET', data=None, doseq=False, **options): + + def _call( + self, subpath, params=None, method="GET", data=None, doseq=False, **options + ): opts = self.options.copy() opts.update(options) @@ -65,91 +69,101 @@ def _call(self, subpath, params=None, method='GET', data=None, doseq=False, **op self._enqueue(request_parts) def _enqueue(self, parts): - if not hasattr(self, '_queue'): + if not hasattr(self, "_queue"): self._queue = [] self._queue.append(parts) def _create_curl(self, url, headers, data): - c = pycurl.Curl() + curl_call = pycurl.Curl() - full_url = '{}://{}{}'.format(self.options['protocol'], self.options['api_base'], url) + full_url = "{}://{}{}".format( + self.options["protocol"], self.options["api_base"], url + ) - c.timeout = self.options['timeout'] - c.protocol = self.options['protocol'] - c.host = self.options['api_base'] - c.path = url - c.full_url = full_url - c.headers = headers - c.data = data + curl_call.timeout = self.options["timeout"] + curl_call.protocol = self.options["protocol"] + curl_call.host = self.options["api_base"] + curl_call.path = url + curl_call.full_url = full_url + curl_call.headers = headers + curl_call.data = data - c.status = -1 - c.body = io.StringIO() - c.response_headers = io.StringIO() + curl_call.status = -1 + curl_call.body = io.StringIO() + curl_call.response_headers = io.StringIO() - c.setopt(c.URL, c.full_url) - c.setopt(c.TIMEOUT, self.options['timeout']) - c.setopt(c.WRITEFUNCTION, c.body.write) - c.setopt(c.HEADERFUNCTION, c.response_headers.write) + curl_call.setopt(curl_call.URL, curl_call.full_url) + curl_call.setopt(curl_call.TIMEOUT, self.options["timeout"]) + curl_call.setopt(curl_call.WRITEFUNCTION, curl_call.body.write) + curl_call.setopt(curl_call.HEADERFUNCTION, curl_call.response_headers.write) if headers: - c.setopt(c.HTTPHEADER, ['{}: {}'.format(x, y) for x, y in list(headers.items())]) + curl_call.setopt( + curl_call.HTTPHEADER, + ["{}: {}".format(x, y) for x, y in list(headers.items())], + ) if data: - c.data_out = io.StringIO(data) - c.setopt(c.READFUNCTION, c.data_out.getvalue) + curl_call.data_out = io.StringIO(data) + curl_call.setopt(curl_call.READFUNCTION, curl_call.data_out.getvalue) - return c + return curl_call def process_queue(self): """ Processes all API calls since last invocation, returning a list of data in the order the API calls were created """ - m = pycurl.CurlMulti() - m.handles = [] + multi_curl = pycurl.CurlMulti() + multi_curl.handles = [] # Loop the queue and create Curl objects for processing for item in self._queue: - c = self._create_curl(*item) - m.add_handle(c) - m.handles.append(c) + curl_call = self._create_curl(*item) + multi_curl.add_handle(curl_call) + multi_curl.handles.append(curl_call) # Process the collected Curl handles - num_handles = len(m.handles) + num_handles = len(multi_curl.handles) while num_handles: while 1: # Perform the calls - ret, num_handles = m.perform() + ret, num_handles = multi_curl.perform() if ret != pycurl.E_CALL_MULTI_PERFORM: break - m.select(1.0) + multi_curl.select(1.0) # Collect data results = [] - for c in m.handles: - c.status = c.getinfo(c.HTTP_CODE) - if 'Content-Encoding: gzip' in c.response_headers.getvalue(): - c.body = io.StringIO(self._gunzip_body(c.body.getvalue())) - result = {'data': self._digest_result(c.body.getvalue()), 'code': c.status} - if not c.status or c.status >= 400: + for curl_call in multi_curl.handles: + curl_call.status = curl_call.getinfo(curl_call.HTTP_CODE) + if "Content-Encoding: gzip" in curl_call.response_headers.getvalue(): + curl_call.body = io.StringIO( + self._gunzip_body(curl_call.body.getvalue()) + ) + result = { + "data": self._digest_result(curl_call.body.getvalue()), + "code": curl_call.status, + } + if not curl_call.status or curl_call.status >= 400: # Don't throw the exception because some might have succeeded - result['exception'] = Hubspot3ThreadedError(c) + result["exception"] = Hubspot3ThreadedError(curl_call) results.append(result) # cleanup - for c in m.handles: - if hasattr(c, 'data_out'): - c.data_out.close() + for curl_call in multi_curl.handles: + if hasattr(curl_call, "data_out"): + curl_call.data_out.close() - c.body.close() - c.response_headers.close() - c.close() - m.remove_handle(c) + curl_call.body.close() + curl_call.response_headers.close() + curl_call.close() + multi_curl.remove_handle(curl_call) - m.close() - del m.handles + multi_curl.close() + del multi_curl.handles self._queue = [] return results diff --git a/hubspot3/owners.py b/hubspot3/owners.py index a2fe232..00e17bc 100644 --- a/hubspot3/owners.py +++ b/hubspot3/owners.py @@ -1,37 +1,36 @@ """ hubspot owners api """ -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient -OWNERS_API_VERSION = 'v2' +OWNERS_API_VERSION = "v2" class OwnersClient(BaseClient): """allows access to the owners api""" + def _get_path(self, subpath): - return 'owners/{}/owners'.format(OWNERS_API_VERSION) + return "owners/{}/owners".format(OWNERS_API_VERSION) def get_owners(self, **options): """*Only* returns the list of owners, does not include additional metadata""" - return self._call('owners', **options) + return self._call("owners", **options) def get_owner_name_by_id(self, owner_id, **options): """given an id of an owner, return their name""" - owner_name = 'value_missing' + owner_name = "value_missing" owners = self.get_owners() for owner in owners: - if int(owner['ownerId']) == int(owner_id): - owner_name = '{} {}'.format(owner['firstName'], owner['lastName']) + if int(owner["ownerId"]) == int(owner_id): + owner_name = "{} {}".format(owner["firstName"], owner["lastName"]) return owner_name def get_owner_email_by_id(self, owner_id, **options): """given an id of an owner, return their email""" - owner_email = 'value_missing' + owner_email = "value_missing" owners = self.get_owners() for owner in owners: - if int(owner['ownerId']) == int(owner_id): - owner_email = owner['email'] + if int(owner["ownerId"]) == int(owner_id): + owner_email = owner["email"] return owner_email diff --git a/hubspot3/prospects.py b/hubspot3/prospects.py index 9da41b4..3735a0c 100644 --- a/hubspot3/prospects.py +++ b/hubspot3/prospects.py @@ -1,12 +1,10 @@ """ hubspot prospects client """ -from hubspot3.base import ( - BaseClient -) +from hubspot3.base import BaseClient -PROSPECTS_API_VERSION = 'v1' +PROSPECTS_API_VERSION = "v1" class ProspectsClient(BaseClient): @@ -18,9 +16,10 @@ class ProspectsClient(BaseClient): Questions, comments, etc: http://docs.hubapi.com/wiki/Discussion_Group. """ - def _get_path(self, method): + + def _get_path(self, subpath): """returns the given path for the api call""" - return 'prospects/{}/{}'.format(PROSPECTS_API_VERSION, method) + return "prospects/{}/{}".format(PROSPECTS_API_VERSION, subpath) def get_prospects(self, offset=None, orgoffset=None, limit=None): """ @@ -33,21 +32,21 @@ def get_prospects(self, offset=None, orgoffset=None, limit=None): """ params = {} if limit: - params['count'] = limit + params["count"] = limit if offset: - params['timeOffset'] = offset - params['orgOffset'] = orgoffset + params["timeOffset"] = offset + params["orgOffset"] = orgoffset - return self._call('timeline', params) + return self._call("timeline", params) def get_company(self, company_slug): """Return the specific named organization for the given API key, if we find a match.""" - return self._call('timeline/{}'.format(company_slug)) + return self._call("timeline/{}".format(company_slug)) def get_options_for_query(self, query): """This method allows for discovery of prospects with partial names.""" - return self._call('typeahead/', {'q': query}) + return self._call("typeahead/", {"q": query}) def search_prospects(self, search_type, query, offset=None, orgoffset=None): """ @@ -58,26 +57,28 @@ def search_prospects(self, search_type, query, offset=None, orgoffset=None): This method is intended to be called with one of the outputs from the get_options_for_query method above. """ - params = {'q': query} + params = {"q": query} if offset and orgoffset: - params['orgOffset'] = orgoffset - params['timeOffset'] = offset + params["orgOffset"] = orgoffset + params["timeOffset"] = offset - return self._call('search/{}'.format(search_type), params) + return self._call("search/{}".format(search_type), params) def get_hidden_prospects(self): """Return the list of prospects hidden by the customer (or this API), if any.""" - return self._call('filters') + return self._call("filters") def hide_prospect(self, company_name): """Hides the given prospect from the user interface.""" return self._call( - 'filters', - data=('organization={}'.format(company_name)), - method='POST', - content_type='application/x-www-form-urlencoded' + "filters", + data=("organization={}".format(company_name)), + method="POST", + content_type="application/x-www-form-urlencoded", ) def unhide_prospect(self, company_name): """Un-hides, i.e. displays, the given prospect in the user interface.""" - return self._call('filters', data={'organization': company_name}, method='DELETE') + return self._call( + "filters", data={"organization": company_name}, method="DELETE" + ) diff --git a/hubspot3/settings.py b/hubspot3/settings.py index 062fe10..21b5089 100644 --- a/hubspot3/settings.py +++ b/hubspot3/settings.py @@ -1,15 +1,11 @@ """ hubspot settings api """ -from hubspot3.base import ( - BaseClient -) -from hubspot3.error import ( - HubspotError -) +from hubspot3.base import BaseClient +from hubspot3.error import HubspotError -SETTINGS_API_VERSION = 'v1' +SETTINGS_API_VERSION = "v1" class SettingsClient(BaseClient): @@ -22,33 +18,36 @@ class SettingsClient(BaseClient): Comments, questions, etc: http://docs.hubapi.com/wiki/Discussion_Group """ + def _get_path(self, subpath): - return 'settings/{}/{}'.format(SETTINGS_API_VERSION, subpath) + return "settings/{}/{}".format(SETTINGS_API_VERSION, subpath) def get_settings(self, **options): """Returns the settings we know about for this API key.""" - return self._call('settings', **options) + return self._call("settings", **options) def get_setting(self, name, **options): """Returns the specific requested setting name, if found.""" - params = {'name': name} - return self._call('settings', params=params, **options) + params = {"name": name} + return self._call("settings", params=params, **options) def update_setting(self, data, **options): """Updates a specific setting for this API key.""" params = {} - if data['name']: - params['name'] = data['name'] - if data['value']: - params['value'] = data['value'] + if data["name"]: + params["name"] = data["name"] + if data["value"]: + params["value"] = data["value"] - return self._call('settings', params=params, data=data, method='POST', **options) + return self._call( + "settings", params=params, data=data, method="POST", **options + ) def delete_setting(self, name, **options): """"Deletes" a specific setting by emptying out its value.""" params = {} if name: - params['name'] = name + params["name"] = name else: - raise HubspotError('Setting name required.') - return self._call('settings', params=params, method='DELETE', **options) + raise HubspotError("Setting name required.", "settings") + return self._call("settings", params=params, method="DELETE", **options) diff --git a/hubspot3/test/helper.py b/hubspot3/test/helper.py index c0f899c..a4d414a 100644 --- a/hubspot3/test/helper.py +++ b/hubspot3/test/helper.py @@ -3,7 +3,7 @@ def get_options(): - filename = 'test_credentials.json' + filename = "test_credentials.json" path = os.path.join(os.path.dirname(__file__), filename) options = {} if os.path.exists(path): @@ -11,28 +11,32 @@ def get_options(): raw_text = open(path).read() except IOError: raise Exception( - 'Unable to open \'{}\' for integration tests.\n' - 'If this file exists, then you are indicating you want to over' - 'ride the standard \'demo\' creds with your own.\n' - 'However, it is currently inaccessible so that is a problem.'.format(filename) + "Unable to open '{}' for integration tests.\n" + "If this file exists, then you are indicating you want to over" + "ride the standard 'demo' creds with your own.\n" + "However, it is currently inaccessible so that is a problem.".format( + filename + ) ) try: options = json.loads(raw_text) except ValueError: raise Exception( - '\'{}\' doesn\'t appear to be valid json!\n' - 'If this file exists, then you are indicating you want to override ' - 'the standard \'demo\' creds with your own.\nHowever, if I can\'t unde' - 'rstand the json inside of it, then that is a problem.'.format(filename) + "'{}' doesn't appear to be valid json!\n" + "If this file exists, then you are indicating you want to override " + "the standard 'demo' creds with your own.\nHowever, if I can't unde" + "rstand the json inside of it, then that is a problem.".format(filename) ) - if not options.get('api_key') and not options.get('hapikey'): + if not options.get("api_key") and not options.get("hapikey"): raise Exception( - '\'{}\' seems to have no \'api_key\' or \'access_token\' specified!\n' - 'If this file exists, then you are indicating you want to override' - 'the standard \'demo\' creds with your own.\nHowever, I\'ll need at ' - 'least an API key to work with, or it definitely won\'t work.'.format(filename) + "'{}' seems to have no 'api_key' or 'access_token' specified!\n" + "If this file exists, then you are indicating you want to override" + "the standard 'demo' creds with your own.\nHowever, I'll need at " + "least an API key to work with, or it definitely won't work.".format( + filename + ) ) - options['api_key'] = options.get('api_key') or options.get('hapikey') + options["api_key"] = options.get("api_key") or options.get("hapikey") return options diff --git a/hubspot3/test/logger.py b/hubspot3/test/logger.py index 2b6915e..ce60289 100644 --- a/hubspot3/test/logger.py +++ b/hubspot3/test/logger.py @@ -5,21 +5,25 @@ def configure_log(): - log = logging.getLogger('hubspot3') + log = logging.getLogger("hubspot3") log.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(os.path.join( - os.path.dirname(__file__), 'test_run.log'), mode='w') + file_handler = logging.FileHandler( + os.path.join(os.path.dirname(__file__), "test_run.log"), mode="w" + ) file_handler.setLevel(logging.DEBUG) console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) - formatter = logging.Formatter('%(asctime)s %(levelname)-5s %(name)s === %(message)s') + formatter = logging.Formatter( + "%(asctime)s %(levelname)-5s %(name)s === %(message)s" + ) file_handler.setFormatter(formatter) formatter = logging.Formatter( - '%(asctime)s %(levelname)-5s %(name)s === %(message)s', datefmt='%M:%S') + "%(asctime)s %(levelname)-5s %(name)s === %(message)s", datefmt="%M:%S" + ) console_handler.setFormatter(formatter) log.addHandler(file_handler) diff --git a/hubspot3/test/test_base.py b/hubspot3/test/test_base.py index 0bcf228..057df1b 100644 --- a/hubspot3/test/test_base.py +++ b/hubspot3/test/test_base.py @@ -1,20 +1,14 @@ import unittest import json import zlib -from collections import ( - defaultdict -) -from hubspot3.base import ( - BaseClient -) -from hubspot3.error import ( - HubspotError -) +from collections import defaultdict +from hubspot3.base import BaseClient +from hubspot3.error import HubspotError class TestBaseClient(BaseClient): def _get_path(self, subpath): - return 'unit_path/{}'.format(subpath) + return "unit_path/{}".format(subpath) class TestResult(object): @@ -28,51 +22,56 @@ def getheaders(self): class BaseTest(unittest.TestCase): def setUp(self): - self.client = TestBaseClient('unit_api_key') + self.client = TestBaseClient("unit_api_key") def tearDown(self): pass def test_prepare_request(self): - subpath = 'unit_sub_path' - params = {'duplicate': ['key', 'value']} + subpath = "unit_sub_path" + params = {"duplicate": ["key", "value"]} data = None opts = {} doseq = False # with doseq=False we should encode the array # so duplicate=[key,value] - url, headers, data = self.client._prepare_request(subpath, params, data, opts, doseq) - self.assertTrue('duplicate=%5B%27key%27%2C+%27value%27%5D' in url) + url, headers, data = self.client._prepare_request( + subpath, params, data, opts, doseq + ) + self.assertTrue("duplicate=%5B%27key%27%2C+%27value%27%5D" in url) # with doseq=True the values will be split and assigned their own key # so duplicate=key&duplicate=value doseq = True - url, headers, data = self.client._prepare_request(subpath, params, data, opts, doseq) + url, headers, data = self.client._prepare_request( + subpath, params, data, opts, doseq + ) print(url) - self.assertTrue('duplicate=key&duplicate=' in url) + self.assertTrue("duplicate=key&duplicate=" in url) def test_call(self): - client = TestBaseClient('key', api_base='base', env='hudson') + client = TestBaseClient("key", api_base="base", env="hudson") client.sleep_multiplier = .02 client._create_request = lambda *args: None counter = dict(count=0) - args = ('/my-api-path', {'bebop': 'rocksteady'}) - kwargs = dict(method='GET', data={}, doseq=False, number_retries=3) + args = ("/my-api-path", {"bebop": "rocksteady"}) + kwargs = dict(method="GET", data={}, doseq=False, number_retries=3) def execute_request_with_retries(a, b): - counter['count'] += 1 - if counter['count'] < 2: + counter["count"] += 1 + if counter["count"] < 2: raise HubspotError(defaultdict(str), defaultdict(str)) else: - return TestResult(body='SUCCESS') + return TestResult(body="SUCCESS") + client._execute_request_raw = execute_request_with_retries # This should fail once, and then succeed result = client._call(*args, **kwargs) - self.assertEqual(2, counter['count']) - self.assertEqual('SUCCESS', result) + self.assertEqual(2, counter["count"]) + self.assertEqual("SUCCESS", result) def execute_request_failed(a, b): raise HubspotError(defaultdict(str), defaultdict(str)) @@ -90,16 +89,16 @@ def test_digest_result(self): """ Test parsing returned data in various forms """ - plain_text = 'Hello Plain Text' + plain_text = "Hello Plain Text" data = self.client._process_body(plain_text, False) self.assertEqual(plain_text, data) raw_json = '{"hello": "json"}' data = json.loads(self.client._process_body(raw_json, False)) # Should parse as json into dict - self.assertEqual(data.get('hello'), 'json') + self.assertEqual(data.get("hello"), "json") payload = zlib.compress('{"hello": "gzipped"}'.encode()) data = json.loads(self.client._process_body(payload, True)) - self.assertEqual(data.get('hello'), 'gzipped') + self.assertEqual(data.get("hello"), "gzipped") diff --git a/hubspot3/test/test_broadcast.py b/hubspot3/test/test_broadcast.py index 9dca5f6..13261db 100644 --- a/hubspot3/test/test_broadcast.py +++ b/hubspot3/test/test_broadcast.py @@ -1,15 +1,8 @@ import time import unittest -from nose.plugins.attrib import ( - attr -) -from hubspot3.test import ( - helper -) -from hubspot3.broadcast import ( - Broadcast, - BroadcastClient -) +from nose.plugins.attrib import attr +from hubspot3.test import helper +from hubspot3.broadcast import Broadcast, BroadcastClient class BroadcastClientTest(unittest.TestCase): @@ -19,6 +12,7 @@ class BroadcastClientTest(unittest.TestCase): Questions, comments: http://docs.hubapi.com/wiki/Discussion_Group """ + def setUp(self): self.client = BroadcastClient(**helper.get_options()) self.broadcast_guids = None @@ -28,17 +22,17 @@ def tearDown(self): if self.broadcast_guids: list(map(self.client.cancel_broadcast, self.broadcast_guids)) - @attr('api') + @attr("api") def test_get_broadcasts(self): # Should fetch at least 1 broadcast on the test portal 62515 broadcasts = self.client.get_broadcasts(limit=1) self.assertTrue(len(broadcasts) > 0) broadcast = broadcasts[0].to_dict() - self.assertIsNotNone(broadcast['channelGuid']) - print('\n\nFetched some broadcasts') + self.assertIsNotNone(broadcast["channelGuid"]) + print("\n\nFetched some broadcasts") - broadcast_guid = broadcast['broadcastGuid'] + broadcast_guid = broadcast["broadcastGuid"] # Re-fetch the broadcast using different call bcast = self.client.get_broadcast(broadcast_guid) # Should have expected fields @@ -46,28 +40,30 @@ def test_get_broadcasts(self): self.assertIsNotNone(bcast.channel_guid) self.assertIsNotNone(bcast.status) - @attr('api') + @attr("api") def test_get_channels(self): # Fetch older channels ensured to exist channels = self.client.get_channels(current=True) self.assertTrue(len(channels) > 0) - @attr('api') + @attr("api") def test_create_broadcast(self): - content = dict(body='Test hubspot3 unit tests http://www.hubspot.com') + content = dict(body="Test hubspot3 unit tests http://www.hubspot.com") channels = self.client.get_channels(current=True, publish_only=True) if len(channels) == 0: - self.fail('Failed to find a publishable channel') + self.fail("Failed to find a publishable channel") channel = channels[0] # Get a trigger in the future trigger_at = int(time.time() + 6000) * 1000 - bcast = Broadcast({ - 'content': content, - 'triggerAt': trigger_at, - 'channelGuid': channel.channel_guid - }) + bcast = Broadcast( + { + "content": content, + "triggerAt": trigger_at, + "channelGuid": channel.channel_guid, + } + ) try: resp = self.client.create_broadcast(bcast) @@ -78,8 +74,8 @@ def test_create_broadcast(self): self.broadcast_guids = [] self.broadcast_guids.append(broadcast.broadcast_guid) except Exception as e: - self.fail('Should not have raised exception: {}'.format(e)) + self.fail("Should not have raised exception: {}".format(e)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/hubspot3/test/test_error.py b/hubspot3/test/test_error.py index cffcee4..c51d46d 100644 --- a/hubspot3/test/test_error.py +++ b/hubspot3/test/test_error.py @@ -1,9 +1,5 @@ -from hubspot3.error import ( - HubspotError -) -from nose.tools import ( - ok_ -) +from hubspot3.error import HubspotError +from nose.tools import ok_ class MockResult(object): @@ -11,32 +7,33 @@ class MockResult(object): Null Object pattern to prevent Null reference errors when there is no result """ + def __init__(self): self.status = 0 - self.body = '' - self.msg = '' - self.reason = '' + self.body = "" + self.msg = "" + self.reason = "" def test_unicode_error(): result = MockResult() - result.body = 'A HapiException with unicode \\u8131 \xe2\x80\xa2\t' - result.reason = 'Why must everything have a reason?' + result.body = "A HapiException with unicode \\u8131 \xe2\x80\xa2\t" + result.reason = "Why must everything have a reason?" request = {} - for key in ('method', 'host', 'url', 'timeout', 'data', 'headers'): - request[key] = '' - request['url'] = 'http://adomain/with-unicode-\u8131' + for key in ("method", "host", "url", "timeout", "data", "headers"): + request[key] = "" + request["url"] = "http://adomain/with-unicode-\u8131" # Note the following line is missing the 'u' modifier on the string, # this is intentional to simulate poorly formatted input that should # still be handled without an exception - request['data'] = 'A HapiException with unicode \\u8131 \xe2\x80\xa2' - request['headers'] = {'Cookie': 'with unicode \\u8131 \xe2\x80\xa2'} + request["data"] = "A HapiException with unicode \\u8131 \xe2\x80\xa2" + request["headers"] = {"Cookie": "with unicode \\u8131 \xe2\x80\xa2"} exc = HubspotError(result, request) - ok_(request['url'] in exc) + ok_(request["url"] in exc) ok_(result.reason in exc) def test_error_with_no_result_or_request(): - exc = HubspotError(None, None, 'a silly error') - ok_('a silly error' in exc) + exc = HubspotError(None, None, "a silly error") + ok_("a silly error" in exc) diff --git a/hubspot3/test/test_keywords.py b/hubspot3/test/test_keywords.py index 1870932..2de4609 100644 --- a/hubspot3/test/test_keywords.py +++ b/hubspot3/test/test_keywords.py @@ -3,15 +3,9 @@ import uuid import json -from nose.plugins.attrib import ( - attr -) -from . import ( - helper -) -from hubspot3.keywords import ( - KeywordsClient -) +from nose.plugins.attrib import attr +from . import helper +from hubspot3.keywords import KeywordsClient class KeywordsClientTest(unittest.TestCase): @@ -21,57 +15,60 @@ class KeywordsClientTest(unittest.TestCase): Questions, comments: http://docs.hubapi.com/wiki/Discussion_Group """ + def setUp(self): self.client = KeywordsClient(**helper.get_options()) self.keyword_guids = None def tearDown(self): - if (self.keyword_guids): - list(map( - lambda keyword_guid: self.client.delete_keyword(keyword_guid), - self.keyword_guids - )) - - @attr('api') + if self.keyword_guids: + list( + map( + lambda keyword_guid: self.client.delete_keyword(keyword_guid), + self.keyword_guids, + ) + ) + + @attr("api") def test_get_keywords(self): keywords = self.client.get_keywords() self.assertTrue(len(keywords)) - print(('\n\nGot some keywords: {}'.format(json.dumps(keywords)))) + print(("\n\nGot some keywords: {}".format(json.dumps(keywords)))) - @attr('api') + @attr("api") def test_get_keyword(self): keywords = self.client.get_keywords() if len(keywords) < 1: - self.fail('No keywords available for test.') + self.fail("No keywords available for test.") keyword = keywords[0] - print(('\n\nGoing to get a specific keyword: {}'.format(keyword))) + print(("\n\nGoing to get a specific keyword: {}".format(keyword))) - result = self.client.get_keyword(keyword['keyword_guid']) + result = self.client.get_keyword(keyword["keyword_guid"]) self.assertEqual(keyword, result) - print(('\n\nGot a single matching keyword: {}'.format(keyword['keyword_guid']))) + print(("\n\nGot a single matching keyword: {}".format(keyword["keyword_guid"]))) - @attr('api') + @attr("api") def test_add_keyword(self): keyword = [] # Add a single keyword to this self, it is a string with a uuid added because a string with # a random number appended to it has too high of a collision rate - keyword.append('hubspot3_test_keyword{}'.format(str(uuid.uuid4()))) + keyword.append("hubspot3_test_keyword{}".format(str(uuid.uuid4()))) # copy the keyword into 'result' after the client adds it result = self.client.add_keyword(keyword) # make sure 'result' has one keyword in it - self.assertEqual(len(result['keywords']), 1) + self.assertEqual(len(result["keywords"]), 1) - print('\n\nAdded keyword: {}'.format(json.dumps(result))) + print("\n\nAdded keyword: {}".format(json.dumps(result))) # holds the guid of the keyword being added self.keyword_guid = [] # get the keyword's guid - self.keyword_guid.append(result['keywords'][0]['keyword_guid']) + self.keyword_guid.append(result["keywords"][0]["keyword_guid"]) # now check if the keyword is in the client @@ -79,14 +76,14 @@ def test_add_keyword(self): check = self.client.get_keywords() # filter 'check' if it is in this self - check = [p for p in check if p['keyword_guid'] in self.keyword_guid] + check = [p for p in check if p["keyword_guid"] in self.keyword_guid] # check if it was filtered. If it was, it is in the client self.assertEqual(len(check), 1) - print('\n\nSaved keyword {}'.format(json.dumps(check))) + print("\n\nSaved keyword {}".format(json.dumps(check))) - @attr('api') + @attr("api") def test_add_keywords(self): # Add multiple Keywords in one API call. keywords = [] @@ -94,7 +91,7 @@ def test_add_keywords(self): # A string with a random number between 0 and 1000 as a test keyword has too high # of a collision rate. # switched test string to a uuid to decrease collision chance. - keywords.append('hubspot3_test_keyword{}'.format(str(uuid.uuid4()))) + keywords.append("hubspot3_test_keyword{}".format(str(uuid.uuid4()))) # copy the keywords into 'result' after the client adds them result = self.client.add_keywords(keywords) @@ -105,7 +102,7 @@ def test_add_keywords(self): # make and fill a list of 'keyword's guid's self.keyword_guids = [] for keyword in result: - self.keyword_guids.append(keyword['keyword_guid']) + self.keyword_guids.append(keyword["keyword_guid"]) # This next section removes keywords from 'keywords' that are already in self by # checking the guid's. If none of the keywords in 'keywords' are already @@ -114,64 +111,66 @@ def test_add_keywords(self): # Make sure they're in the list now keywords = self.client.get_keywords() - keywords = [x for x in keywords if x['keyword_guid'] in self.keyword_guids] + keywords = [x for x in keywords if x["keyword_guid"] in self.keyword_guids] self.assertEqual(len(keywords), 10) - print('\n\nAdded multiple keywords: {}'.format(keywords)) + print("\n\nAdded multiple keywords: {}".format(keywords)) - @attr('api') + @attr("api") def test_delete_keyword(self): # Delete multiple keywords in one API call. - keyword = 'hubspot3_test_keyword{}'.format(str(uuid.uuid4())) + keyword = "hubspot3_test_keyword{}".format(str(uuid.uuid4())) result = self.client.add_keyword(keyword) - keywords = result['keywords'] + keywords = result["keywords"] first_keyword = keywords[0] - print('\n\nAbout to delete a keyword, result= {}'.format(json.dumps(result))) + print("\n\nAbout to delete a keyword, result= {}".format(json.dumps(result))) - self.client.delete_keyword(first_keyword['keyword_guid']) + self.client.delete_keyword(first_keyword["keyword_guid"]) # Make sure it's not in the list now keywords = self.client.get_keywords() - keywords = [x for x in keywords if x['keyword_guid'] == first_keyword['keyword_guid']] + keywords = [ + x for x in keywords if x["keyword_guid"] == first_keyword["keyword_guid"] + ] self.assertTrue(len(keywords) == 0) - print('\n\nDeleted keyword {}'.format(json.dumps(first_keyword))) + print("\n\nDeleted keyword {}".format(json.dumps(first_keyword))) - @attr('api') + @attr("api") def test_utf8_keywords(self): # Start with base utf8 characters # TODO: Fails when adding simplified chinese char: 广 or cyrillic: л - utf8_keyword_bases = ['é', 'ü'] + utf8_keyword_bases = ["é", "ü"] keyword_guids = [] for utf8_keyword_base in utf8_keyword_bases: - original_keyword = '{} - {}'.format(utf8_keyword_base, str(uuid.uuid4())) + original_keyword = "{} - {}".format(utf8_keyword_base, str(uuid.uuid4())) result = self.client.add_keyword(original_keyword) - print('\n\nAdded keyword: {}'.format(json.dumps(result))) + print("\n\nAdded keyword: {}".format(json.dumps(result))) print(result) - keywords_results = result.get('keywords') + keywords_results = result.get("keywords") keyword_result = keywords_results[0] - self.assertTrue(keyword_result['keyword_guid']) - keyword_guids.append(keyword_result['keyword_guid']) + self.assertTrue(keyword_result["keyword_guid"]) + keyword_guids.append(keyword_result["keyword_guid"]) - actual_keyword = keyword_result['keyword'] + actual_keyword = keyword_result["keyword"] # Convert to utf-8 to compare strings. Returned string is \x-escaped if isinstance(original_keyword, str): original_unicode_keyword = original_keyword else: - original_unicode_keyword = original_keyword.decode('utf-8') + original_unicode_keyword = original_keyword.decode("utf-8") if isinstance(actual_keyword, str): actual_unicode_keyword = actual_keyword else: - actual_unicode_keyword = actual_keyword.decode('utf-8') + actual_unicode_keyword = actual_keyword.decode("utf-8") self.assertEqual(actual_unicode_keyword, original_unicode_keyword) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/hubspot3/test/test_leads.py b/hubspot3/test/test_leads.py index 7f24de2..a63adc1 100644 --- a/hubspot3/test/test_leads.py +++ b/hubspot3/test/test_leads.py @@ -1,14 +1,9 @@ import unittest -from . import ( - helper -) -from hubspot3.leads import ( - LeadsClient -) +from . import helper +from hubspot3.leads import LeadsClient class LeadsClientTest(unittest.TestCase): - def setUp(self): self.client = LeadsClient(**helper.get_options()) @@ -17,17 +12,19 @@ def tearDown(self): def test_camelcased_params(self): in_options = { - 'sort': 'fce.convert_date', - 'search': 'BlahBlah', - 'time_pivot': 'last_modified_at', - 'is_not_imported': True} + "sort": "fce.convert_date", + "search": "BlahBlah", + "time_pivot": "last_modified_at", + "is_not_imported": True, + } out_options = { - 'sort': 'fce.convertDate', - 'search': 'BlahBlah', - 'timePivot': 'lastModifiedAt', - 'isNotImported': 'true'} + "sort": "fce.convertDate", + "search": "BlahBlah", + "timePivot": "lastModifiedAt", + "isNotImported": "true", + } self.assertEqual(out_options, self.client.camelcase_search_options(in_options)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/hubspot3/test/test_settings.py b/hubspot3/test/test_settings.py index 8fb2999..a12551e 100644 --- a/hubspot3/test/test_settings.py +++ b/hubspot3/test/test_settings.py @@ -1,14 +1,8 @@ import unittest import json -from nose.plugins.attrib import ( - attr -) -from hubspot3.test import ( - helper -) -from hubspot3.settings import ( - SettingsClient -) +from nose.plugins.attrib import attr +from hubspot3.test import helper +from hubspot3.settings import SettingsClient class SettingsClientTest(unittest.TestCase): @@ -20,47 +14,52 @@ class SettingsClientTest(unittest.TestCase): Questions, comments: http://docs.hubapi.com/wiki/Discussion_Group """ + def setUp(self): self.client = SettingsClient(**helper.get_options()) def tearDown(self): pass - @attr('api') + @attr("api") def test_get_settings(self): # Get all settings, a lengthy list typically. settings = self.client.get_settings() self.assertTrue(len(settings)) - print('\n\nGot some settings: {}'.format(json.dumps(settings))) + print("\n\nGot some settings: {}".format(json.dumps(settings))) - @attr('api') + @attr("api") def test_get_setting(self): # Get a specific named setting. - name = 'test_name' + name = "test_name" settings = self.client.get_setting(name) self.assertTrue(len(settings)) - print('\n\nGot a specific setting: {}, giving {}'.format(name, json.dumps(settings))) + print( + "\n\nGot a specific setting: {}, giving {}".format( + name, json.dumps(settings) + ) + ) - @attr('api') + @attr("api") def test_add_setting(self): # Add or update a specific setting. - data = {'name': 'test_name', 'value': 'test_value'} + data = {"name": "test_name", "value": "test_value"} self.client.update_setting(data) # This is just a 201 response (or 500), no contents. - print('\n\nUpdated setting: {}.'.format(data['name'])) + print("\n\nUpdated setting: {}.".format(data["name"])) - @attr('api') + @attr("api") def test_delete_setting(self): # Deletes a specific setting, emptying out its value. - name = 'test_name' + name = "test_name" self.client.delete_setting(name) # This is just a 201 response (or 500), no contents. - print('\n\nDeleted setting: {}.'.format(name)) + print("\n\nDeleted setting: {}.".format(name)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/hubspot3/utils.py b/hubspot3/utils.py index bda1b6c..0ba672d 100644 --- a/hubspot3/utils.py +++ b/hubspot3/utils.py @@ -3,9 +3,7 @@ """ import requests import logging -from hubspot3.globals import ( - BASE_URL -) +from hubspot3.globals import BASE_URL class NullHandler(logging.Handler): @@ -21,11 +19,8 @@ def get_log(name): def auth_checker(access_token): """Do a simple api request using the access token""" - url = ( - '{}/contacts/v1/lists/all/contacts/all?count=1&offset=0&access_token={}'.format( - BASE_URL, - access_token - ) + url = "{}/contacts/v1/lists/all/contacts/all?count=1&offset=0&access_token={}".format( + BASE_URL, access_token ) result = requests.get(url) return result.status_code @@ -33,11 +28,10 @@ def auth_checker(access_token): def refresh_access_token(refresh_token, client_id): """Refreshes an OAuth access token""" - payload = 'refresh_token={}&client_id={}&grant_type=refresh_token'.format( - refresh_token, - client_id + payload = "refresh_token={}&client_id={}&grant_type=refresh_token".format( + refresh_token, client_id ) - url = '{}/auth/v1/refresh'.format(BASE_URL) + url = "{}/auth/v1/refresh".format(BASE_URL) result = requests.post(url, data=payload) return result.text @@ -46,7 +40,7 @@ def force_utf8(raw): """Will force the string to convert to valid utf8""" string = raw try: - string = string.decode('utf-8', 'ignore') + string = string.decode("utf-8", "ignore") except Exception: pass return string @@ -54,13 +48,17 @@ def force_utf8(raw): def prettify(obj_with_props, id_key): prettified = { - prop: obj_with_props['properties'][prop]['value'] - for prop in obj_with_props['properties'] + prop: obj_with_props["properties"][prop]["value"] + for prop in obj_with_props["properties"] } - prettified['id'] = obj_with_props[id_key] + prettified["id"] = obj_with_props[id_key] try: - prettified.update({assoc: obj_with_props['associations'][assoc] - for assoc in obj_with_props['associations']}) + prettified.update( + { + assoc: obj_with_props["associations"][assoc] + for assoc in obj_with_props["associations"] + } + ) except KeyError: pass diff --git a/requirements.txt b/requirements.txt index c6c5384..f003bfc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ nose -pycurl \ No newline at end of file +pycurl +requests \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..885b668 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[mypy] +python_version = 3.5 +disallow_untyped_defs = False +ignore_missing_imports = True + +[flake8] +ignore = N802,N807,W503 +max-line-length = 100 +max-complexity = 20 diff --git a/setup.py b/setup.py index 21084c0..3ee2200 100755 --- a/setup.py +++ b/setup.py @@ -2,31 +2,25 @@ """ pip setup file """ -from hubspot3.globals import ( - __version__ -) -from setuptools import ( - setup -) +from hubspot3.globals import __version__ +from setuptools import setup setup( - name='hubspot3', + name="hubspot3", version=__version__, description=( - 'A python wrapper around HubSpot\'s APIs, for python 3.' - ' Built initially around hapipy, but heavily modified.' + "A python wrapper around HubSpot's APIs, for python 3." + " Built initially around hapipy, but heavily modified." ), - author='HubSpot Dev Team, Jacobi Petrucciani', - author_email='jacobi@mimirhq.com', - url='https://github.com/jpetrucciani/hubspot3.git', - download_url='https://github.com/jpetrucciani/hubspot3.git', - license='LICENSE', - packages=['hubspot3', 'hubspot3.mixins'], - install_requires=[ - 'nose==1.3.6' - ], + author="HubSpot Dev Team, Jacobi Petrucciani", + author_email="jacobi@mimirhq.com", + url="https://github.com/jpetrucciani/hubspot3.git", + download_url="https://github.com/jpetrucciani/hubspot3.git", + license="LICENSE", + packages=["hubspot3", "hubspot3.mixins"], + install_requires=["nose"], zip_safe=False, - test_suite='nose.collector', - tests_require=['nose'] + test_suite="nose.collector", + tests_require=["nose"], )