Skip to content

Commit

Permalink
version bump. general cleanup of variable names and comments. adding …
Browse files Browse the repository at this point in the history
…some typings!
  • Loading branch information
jpetrucciani committed Apr 15, 2019
1 parent 0e60a5b commit 07325c0
Show file tree
Hide file tree
Showing 19 changed files with 126 additions and 152 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ List of available clients
companies.py: hubspot companies api
contact_lists.py: hubspot contact lists api
contacts.py: hubspot contacts api
crm_associations.py: hubspot crm_associations api
crm_pipelines.py: hubspot crm_pipelines api
deals.py: hubspot deals api
engagements.py: hubspot engagements api
Expand Down
4 changes: 2 additions & 2 deletions docs/hubspot3/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ Below is an example of how to deal with pagination from the DealsClient included
"""
finished = False
output = []
querylimit = 100 # max according to the docs
query_limit = 100 # max according to the docs
while not finished:
params = {
"count": querylimit,
"count": query_limit,
"offset": offset,
"includePropertyVersions": include_versions,
}
Expand Down
67 changes: 18 additions & 49 deletions hubspot3/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import http.client
import json
import logging
import sys
import time
import traceback
import zlib
Expand All @@ -23,9 +22,6 @@
)


_PYTHON25 = sys.version_info < (2, 6)


class BaseClient(object):
"""Base abstract object for interacting with the HubSpot APIs"""

Expand All @@ -50,24 +46,19 @@ def __init__(
mixins.reverse()
for mixin_class in mixins:
if mixin_class not in self.__class__.__bases__:
self.__class__.__bases__ = (
mixin_class,
) + 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.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.")
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}
if not _PYTHON25:
self.options["timeout"] = timeout
self.options["timeout"] = timeout
self.options.update(extra_options)
self._prepare_connection_type()

Expand All @@ -83,15 +74,14 @@ def _prepare_connection_type(self):
self.options["api_base"] = parts[-1]

def _get_path(self, subpath):
"""get the full api url for the given subpath on this client"""
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

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)

Expand All @@ -104,9 +94,7 @@ def _prepare_request(
if query and not query.startswith("&"):
query = "&" + query
url = opts.get("url") or "/{}?{}{}".format(
self._get_path(subpath),
urllib.parse.urlencode(params, doseq),
query,
self._get_path(subpath), urllib.parse.urlencode(params, doseq), query
)
headers = opts.get("headers") or {}
headers.update(
Expand All @@ -116,9 +104,7 @@ def _prepare_request(
}
)
if self.access_token:
headers.update(
{"Authorization": "Bearer {}".format(self.access_token)}
)
headers.update({"Authorization": "Bearer {}".format(self.access_token)})

if data and headers["Content-Type"] == "application/json":
data = json.dumps(data)
Expand All @@ -134,8 +120,7 @@ def _create_request(self, conn, method, url, headers, data):
"headers": headers,
"host": conn.host,
}
if not _PYTHON25:
params["timeout"] = conn.timeout
params["timeout"] = conn.timeout
return params

def _gunzip_body(self, body):
Expand All @@ -152,14 +137,10 @@ 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
)
possibly_encoded = zlib.decompress(possibly_encoded, 16 + zlib.MAX_WBITS)
except Exception:
pass
result.body = self._process_body(
Expand All @@ -171,11 +152,7 @@ def _execute_request_raw(self, conn, request):
raise HubspotNotFound(result, request)
elif result.status == 401:
raise HubspotUnauthorized(result, request)
elif (
result.status >= 400
and result.status < 500
or result.status == 501
):
elif result.status >= 400 and result.status < 500 or result.status == 501:
raise HubspotBadRequest(result, request)
elif result.status >= 500:
raise HubspotServerError(result, request)
Expand Down Expand Up @@ -231,8 +208,7 @@ def _call_raw(
)

kwargs = {}
if not _PYTHON25:
kwargs["timeout"] = opts["timeout"]
kwargs["timeout"] = opts["timeout"]

num_retries = opts.get("number_retries", 2)

Expand All @@ -251,9 +227,7 @@ def _call_raw(
break
try:
try_count += 1
connection = opts["connection_type"](
opts["api_base"], **kwargs
)
connection = opts["connection_type"](opts["api_base"], **kwargs)
request_info = self._create_request(
connection, method, url, headers, data
)
Expand All @@ -275,15 +249,11 @@ def _call_raw(
decoded = json.loads(token_response)
self.access_token = decoded["access_token"]
self.log.info(
"Retrying with new token {}".format(
self.access_token
)
"Retrying with new token {}".format(self.access_token)
)
except Exception as exception:
self.log.error(
"Unable to refresh access_token: {}".format(
exception
)
"Unable to refresh access_token: {}".format(exception)
)
raise
return self._call_raw(
Expand Down Expand Up @@ -331,11 +301,10 @@ def _call_raw(
raise
self._prepare_request_retry(method, url, headers, data)
self.log.warning(
"HubspotError {} calling {}, retrying".format(
exception, url
)
"HubspotError {} calling {}, retrying".format(exception, url)
)
# exponential back off - wait 0 seconds, 1 second, 3 seconds, 7 seconds, 15 seconds, etc
# 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

Expand Down
46 changes: 26 additions & 20 deletions hubspot3/companies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,51 @@
from hubspot3 import logging_helper
from hubspot3.base import BaseClient
from hubspot3.utils import prettify
from typing import List, Mapping, Optional, Union


COMPANIES_API_VERSION = "2"


class CompaniesClient(BaseClient):
"""
The hubspot3 Companies client uses the _make_request method to call the API
for data. It returns a python object translated from the json return
hubspot3 Companies client
:see: https://developers.hubspot.com/docs/methods/companies/companies-overview
"""

def __init__(self, *args, **kwargs):
super(CompaniesClient, self).__init__(*args, **kwargs)
self.log = logging_helper.get_log("hubspot3.companies")

def _get_path(self, subpath):
def _get_path(self, subpath: str) -> str:
"""get the full api url for the given subpath on this client"""
return "companies/v{}/{}".format(
self.options.get("version") or COMPANIES_API_VERSION, subpath
)

def create(self, data=None, **options):
def create(self, data: Mapping = None, **options) -> Mapping:
"""create a new company"""
data = data or {}
return self._call("companies/", data=data, method="POST", **options)

def update(self, company_id, data=None, **options):
def update(self, company_id: str, data: Mapping = None, **options) -> Mapping:
"""update the given company with data"""
data = data or {}
return self._call(
"companies/{}".format(company_id), data=data, method="PUT", **options
)

def delete(self, company_id, **options):
def delete(self, company_id: str, **options) -> Mapping:
"""delete a company"""
return self._call("companies/{}".format(company_id), method="DELETE", **options)

def get(self, company_id, **options):
def get(self, company_id: str, **options) -> Mapping:
"""get a single company by it's ID"""
return self._call("companies/{}".format(company_id), method="GET", **options)

def search_domain(self, domain, limit=1, extra_properties=None, **options):
def search_domain(
self, domain: str, limit: int = 1, extra_properties: Mapping = None, **options
) -> Mapping:
"""searches for companies by domain name. limit is max'd at 100"""
# default properties to fetch
properties = [
Expand All @@ -69,12 +73,14 @@ def search_domain(self, domain, limit=1, extra_properties=None, **options):
**options,
)

def get_all(self, extra_properties=None, **options):
"""get all companies"""
def get_all(
self, extra_properties: Union[str, List] = None, **options
) -> Optional[List]:
"""get all companies, including extra properties if they are passed in"""
finished = False
output = []
offset = 0
querylimit = 250 # Max value according to docs
query_limit = 250 # Max value according to docs

# default properties to fetch
properties = [
Expand All @@ -101,7 +107,7 @@ def get_all(self, extra_properties=None, **options):
method="GET",
doseq=True,
params={
"limit": querylimit,
"limit": query_limit,
"offset": offset,
"properties": properties,
},
Expand All @@ -119,26 +125,26 @@ def get_all(self, extra_properties=None, **options):

return output

def _get_recent(self, recency_type, **options):
def _get_recent(self, recency_type: str, **options) -> Optional[List]:
"""
Returns either list of recently modified companies or recently created companies,
depending on recency_type passed in. Both API endpoints take identical parameters
and return identical formats, they differ only in the URLs
(companies/recent/created or companies/recent/modified)
@see: https://developers.hubspot.com/docs/methods/companies/get_companies_modified
@see: https://developers.hubspot.com/docs/methods/companies/get_companies_created
:see: https://developers.hubspot.com/docs/methods/companies/get_companies_modified
:see: https://developers.hubspot.com/docs/methods/companies/get_companies_created
"""
finished = False
output = []
offset = 0
querylimit = 250 # Max value according to docs
query_limit = 250 # Max value according to docs

while not finished:
batch = self._call(
"companies/recent/%s" % recency_type,
"companies/recent/{}".format(recency_type),
method="GET",
doseq=True,
params={"count": querylimit, "offset": offset},
params={"count": query_limit, "offset": offset},
**options,
)
output.extend(
Expand All @@ -153,8 +159,8 @@ def _get_recent(self, recency_type, **options):

return output

def get_recently_modified(self, **options):
def get_recently_modified(self, **options) -> Optional[List]:
return self._get_recent("modified", **options)

def get_recently_created(self, **options):
def get_recently_created(self, **options) -> Optional[List]:
return self._get_recent("created", **options)
2 changes: 1 addition & 1 deletion hubspot3/contact_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
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
It returns a python object translated from the json returned
"""

def __init__(self, *args, **kwargs):
Expand Down
6 changes: 3 additions & 3 deletions hubspot3/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
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
API for data. It returns a python object translated from the json returned
"""

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -119,12 +119,12 @@ def get_all(self, extra_properties=None, **options):
finished = False
output = []
offset = 0
querylimit = 100 # Max value according to docs
query_limit = 100 # Max value according to docs
while not finished:
batch = self._call(
"lists/all/contacts/all",
method="GET",
params={"count": querylimit, "vidOffset": offset},
params={"count": query_limit, "vidOffset": offset},
**options
)
output.extend(
Expand Down
Loading

0 comments on commit 07325c0

Please sign in to comment.