jpetrucciani / hubspot3 Goto Github PK
View Code? Open in Web Editor NEWpython3.6+ hubspot client based on hapipy, but modified to use the newer endpoints and non-legacy python
License: MIT License
python3.6+ hubspot client based on hapipy, but modified to use the newer endpoints and non-legacy python
License: MIT License
Hi,
I was really pleased to find this - great work. I was wondering if you had any plans for implementing tickets?
https://developers.hubspot.com/docs/methods/tickets/tickets-overview
If not I'm considering making a start on it even if it is simply only to add a ticket. Just wondered what your thoughts were.
Many thanks
Hey,
I see no way to understand where to start with this package - is there some basic connection and information retrieval example anywhere?
Thanks,
I think it would be better to use requests library instead of making your own off of urllib. There is an endpoint for uploading files that needs to be multi-part. Your current library will not support and will consider a re-write. Probably better to just use requests as most of this stuff is built in.
I am getting the contracts all at once by using
python
hb_client = Hubspot3(api_key=API_KEY)
contacts = hb_client.contacts.get_all()
but I want to be able to paginate this request instead of loading it all in at once. Perhaps 20 at a time. there's no documentation that goes over this.
Thanks for providing this wrapper, it works well for us. However, it's been a while since v3 of Hubspot's API was made the default. I just realized /contacts/v1/contact/vid/6191/profile?hapikey=1111111111111
was used, instead of /crm/v3/objects/contacts/6191
. While v1 works, it may eventually be dropped.
(I discovered this when trying to figure out how to turn off the properties history/versions. The docs for the current API version (v3) said nothing about it.)
I am getting this error when calling client.usage_limits
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-99b56d0c4e75> in <module>
----> 1 client.usage_limits
~/projects_backup/env/lib/python3.7/site-packages/hubspot3/__init__.py in usage_limits(self)
369 current_usage=int(limits["currentUsage"]),
370 fetch_status=limits["fetchStatus"],
--> 371 resets_at=datetime.fromtimestamp(int(limits["resetsAt"]) / 1000),
372 usage_limit=limits["usageLimit"],
373 )
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
In base.py:
if not (self.api_key or self.access_token or self.refresh_token):
raise Exception("Missing required credentials.")
However, obviously Forms API does not need any of these. Instantiating it raises this error. It seems unnecessary, and even risky. (I would not want to send these values to the Forms API.)
My colleagues and I a currently working on extending the contacts API (as mentioned also in #39).
The tests for this (and other) API rely on HubSpot's live demo account. This causes some problems. Mainly the tests are failing at the moment because the demo account has reached its daily limit. We need to wait until the next day, when the limit is reset, to run all tests, hoping is has not been exceeded again.
Furthermore the tests can fail when the live database does not contain the expected records. Two examples: When the database is flushed, all tests relying on the contact from the BASE_CONTACT
would fail. And if there are less than 20 records in the database, testing the limit parameter in the test test_get_all
will not fail but will not test anything useful either since there will always be less than 20 results, regardless of the parameter value.
As a solution we would suggest to mock the API calls and their responses in the current unit tests. We would include the necessary changes (at least for the contact API tests) in the following PR.
Let me know what do you think.
Hi @jpetrucciani, I've submitted a small handful of PRs in the past and I'm a regular user/advocate of the project. The company I work at would like to use the library in a distributed environment with oauth2 authentication, but we've run into contention issues where the different containers essentially fight over access tokens because only one can be active at any given time. This leads to almost every request initially failing with 401 errors and then only working after a new access token has been requested from Hubspot. It would be ideal if we could share the tokens across workers through an in-memory store like Redis to avoid these issues. This is a significant enough change that I thought it would be good to open an issue to discuss the approach before implementing it (and I would be happy to implement it, this isn't a request for you to do so).
The easiest way to initialize the client is through the Hubspot3
class in __init__.py
. This supports four options which are all required for Hubspot's oauth authentication scheme:
client_id
- The client ID of the app that's configured for oauth2 authentication. This is non-sensitive and doesn't change.client_secret
- The client secret for the app. This is sensitive and doesn't change.access_token
- This is a temporary token that provides access to the API as an alternative to using the API key. This is sensitive and expires after 6 hours.refresh_token
- This is a temporary token that is required to generate new access tokens after they have expired. This is sensitive, and although it doesn't explicitly expire, it will rotate regularly through the course of requesting new access tokens.These are stored in a dictionary called auth
on a Hubspot3
instance after initialization, and the arguments are passed into the different client classes when they're accessed as properties. For example, accessing the contacts
property on the client will initialize a new ContactsClient
instance via this code:
@property
def contacts(self):
"""returns a hubspot3 contacts client"""
from hubspot3.contacts import ContactsClient
return ContactsClient(**self.auth, **self.options)
All of the various clients inherit from BaseClient which stores the credentials on instance properties when initialized. The credentials are then used in BaseClient._call_raw()
whenever a request is made, and a new access token is requested if a HubspotUnauthorized
exception is raised. The access_token
and refresh_token
properties are then replaced on the instance by this code:
client = OAuth2Client(**self.options)
refresh_result = client.refresh_tokens(
client_id=self.client_id,
client_secret=self.client_secret,
refresh_token=self.refresh_token,
)
self.access_token = refresh_result["access_token"]
self.refresh_token = refresh_result["refresh_token"]
The OAuth2Client
class handles refreshing the tokens, but it isn't responsible for storage or maintenance of the tokens.
A relatively simple and backwards compatible change is adding two new initialization parameters to the Hubspot3
and BaseClient
classes:
oauth2_token_setter
- A setter with type typing.Callable([BaseClient, typing.Literal['access_token', 'refresh_token'], str, str)
, a signature of callable(base_client, token_type, client_id, token)
, and a default value of lambda self, token_type, client_id, token: setattr(self, token_type, token)
. This would be used to store either of the tokens through either some external mechanism or just on the instance. The client ID is included to accommodate for the possibility of multiple clients being used in the same application.
oauth2_token_getter
- A getter with type typing.Callable([BaseClient, typing.Literal['access_token', 'refresh_token'], str)
, a signature of callable(token_type, client_id)
, and a default value of lambda self, token_type, client_id: getattr(self, token_type)
. This would be used to retrieve either of the tokens through some external mechanism.
The logic in the __init__()
methods of BaseClient
and Hubspot3
as well as the BaseClient._call_raw()
method would then use the setter and getter in place of direct attribute access. The access_token
/refresh_token
attributes would still be used by default, and this should be completely backward compatible while also supporting more sophisticated storage mechanisms.
Apologies for the verbose issue, but I wanted to clearly lay out what I'm proposing. If this sounds good to you, then I'll go ahead and implement it.
The Web Analytics endpoint allows developers to find and filter events associated with a CRM object of any type. Do we have any way to get all the events exported?
URL: https://developers.hubspot.com/docs/api/events/web-analytics
Jacobi,
Firstly, thank you (and other contributors) for this library.
I'm a bit new to python (despite many years as a developer - I'm old ;-) and thought this was a great opportunity to develop my python skills while working on a Hubspot integration project.
My first few attempts to query Hubspot have worked fine. As we are at the design state, I would be expecting to be trialing many different queries and updates, to clarify the approach we'll take to integrations.
I was particularly interested in the CLI approach for this.. Sadly, I haven't had a lot of success so far... and this could easily be a case of my inexperience and environment (I'm working in a WSL environment.. have access to many flavours of linux, but this is by far the most convenient in the workplace).
I believe I've followed the setup instructions correctly (but who isn't blind to a few mistakes)
I think it's the elegance of python-fire that's defeating me at the moment... or maybe understanding the implementation.
The very simple initial call via CLI is as follows:
% hubspot3 --api-key="blahblah" contacts get-all
The innermost exception appears in main.py in _replace_stdin_token, which is executing a return with an uninitialized variable, new_args.
I believe this is because the following 'if' will only initialise new_args if stdin_indices or stdin_keys contain a value based on the value of STDIN_TOKEN
if stdin_indices or stdin_keys:
value = json.load(sys.stdin)
new_args = list(args)
for index in stdin_indices:
new_args[index] = value
for key in stdin_keys:
kwargs[key] = value
--> return new_args, kwargs
No matter what I've tried in terms of cli calls, I get an exception at the above line (here's the run time exception, I will add debug session below)
File "/home/ben/fishscaler/hubspot3/main.py", line 189, in _replace_stdin_token
return new_args, kwargs
UnboundLocalError: local variable 'new_args' referenced before assignment
Below is a debug session, where I think the only explanation is, both stdin_indices and stdin_keys aren't initialised with a value... and working back from there has defeated me so far.
Any light you can shed on this would be appreciated.
Here's my debug session, including a print(str(sys.argv)) ahead of the call to main()
/home/ben/fishscaler/cli.py(9)()
-> if name == 'main':
(Pdb) n
/home/ben/fishscaler/cli.py(10)()
-> sys.argv[0] = re.sub(r'(-script.pyw?|.exe)?$', '', sys.argv[0])
(Pdb) n
/home/ben/fishscaler/cli.py(11)()
-> print(str(sys.argv))
(Pdb) n
['cli.py', '--api-key=blahblah', 'contacts', 'get-all']
/home/ben/fishscaler/cli.py(12)()
-> sys.exit(main())
(Pdb) s
--Call--
/home/ben/fishscaler/hubspot3/main.py(231)main()
-> def main():
(Pdb) c
/home/ben/fishscaler/hubspot3/main.py(179)_replace_stdin_token()
-> index for index, value in enumerate(args) if value == self.STDIN_TOKEN
(Pdb) n
/home/ben/fishscaler/hubspot3/main.py(181)_replace_stdin_token()
-> stdin_keys = [key for key, value in kwargs.items() if value == self.STDIN_TOKEN]
(Pdb) n
/home/ben/fishscaler/hubspot3/main.py(182)_replace_stdin_token()
-> if stdin_indices or stdin_keys:
(Pdb) p str(stdin_indices)
'[]'
(Pdb) p stdin_indices
'[]'
.... my read on this is - these two empty lists will result in a return of uninitialised new_args
I'm afraid I'm getting lost in the ClientCLIWrapper and HubspotCLIWrapper calls..
Please excuse my lack of experience here... I'd really like to use hubspot3, and contribute if I can.
Thanks,
Ben
Hi there,
We have to integrate hubspot in our project and I found your package, great stuff!
However, are you planning to provide an integration for https://developers.hubspot.com/docs/methods/email/transactional_email/single-send-overview as well?
Thanks :)
Hi Jacobi,
Our team has requested support for CRM Associations (child company to parent companies). I'm going test a local prototype this week and aim to propose a PR here to roll it in.
I had thought it would just involve updating a company property, but it has its own endpoint: https://developers.hubspot.com/docs/methods/crm-associations/associate-objects
Adding this issue in case someone wants to team up on this :)
Cheers,
Siobhรกn
Hi Jacobi,
First of all, thanks a lot for your work on this package!
My current client is in the process of migrating to Hubspot, and hubspot3
is helping me a lot in connecting it to our Django-backed website :)
I now realize I am going to need to use the Products API.
Do you have plans to add it soon or not?
Please do not feel any pressure to do so; if not, I'll be happy to do it myself and submit a little PR.
I'm merely asking to avoid duplicated efforts on that matter ^^
Cheers!
Hi -
I was using the code snippet from guysoft to extend the base client to emails (for which thanks!), and I noticed a couple of things:
def get_email_campaigns(self, **options):
params = {**options}
def get_email_campaigns(self, **options):
params = {**options}
output = []
finished = False
while not finished:
page = self._call("campaigns", method="GET", params=params)
params['offset'] = page['offset']
output.extend(page['campaigns'])
if page['hasMore'] == False:
finished = True
return output
Neither of these is issues with the code itself, I don't think, but maybe they'd be useful for the documentation? I'm very much a beginner and I find it helpful to have examples spelled out for me. Thought I'd maybe save someone some time. Because this really is the best python sdk out there for hubspot!
Andrew S.
To get the Forms API submission to work, I had to do the following:
dataToSubmit = dict(data)
dataToSubmit['hs_context'] = json.dumps(context)
# Build the request body, URL encoded
urlEncodedData = urlencode(dataToSubmit, quote_via=quote_plus)
formsClient.submit_form(
portal_id=config.hubspot.HUBSPOT_PORTAL_ID,
form_guid=formGuid,
data=urlEncodedData,
)
I think support for URL Encoding the data makes sense here, this should not be required to be done outside of the class itself. Without this, form submission fails.
According to the documentation:
# Retrying API Calls
By default, hubspot3 will attempt to retry all API calls up to 2 times
upon failure.
If you'd like to override this behavior, you can add a `number_retries`
keyword argument to any Client constructor, or to individual API calls.
However, setting number_retries=0
causes the library to spam out warnings on the first failure, e.g.
Too many retries for /deals/v1/deal/110248399499?
Too many retries for /deals/v1/deal/102249436582?
Too many retries for /deals/v1/deal/102351008917?
When disabling the auto-retry mechanism it is expected that the library will not auto-retry, and will not output anything in regards to retrying.
Can we disable this warning in case of number_retries==0
?
When using the base client (we extended it for the Hubspot Associations API), we see the following in our logs:
2020-05-13 23:09:51,608 [22/#7f68ac2b2ae0] WARNING in base._call_raw:323: Too many retries for /crm-associations/v1/associations/123456/HUBSPOT_DEFINED/1?
2020-05-13 23:09:52,662 [22/#7f68ac2b2ae0] WARNING in base._call_raw:323: Too many retries for /crm-associations/v1/associations/123456/HUBSPOT_DEFINED/1?
2020-05-13 23:09:53,722 [22/#7f68ac2b2ae0] WARNING in base._call_raw:323: Too many retries for /crm-associations/v1/associations/123456/HUBSPOT_DEFINED/1?
Further in the logs, there no log output of any kind explaining what issue was encountered, what HTTP status code was returned, what error was encountered. No exception is thrown, and eventually (after 2-3 tries) the function using the BaseClient just returns None.
Looking at the code:
except HubspotError as exception:
if try_count > num_retries:
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
):
raise
self._prepare_request_retry(method, url, headers, data)
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)
it seems like an exception should have indeed been thrown, so I am not certain why I don't see it outside. Anyways, I see the self.log.warning() call and I wonder why this output is not written to the log.
Is there any way to configure hubspot3
to print this warning to log in case of an error causing a retry?
Hi Jacobi,
Thanks so much for sharing this great project!
I've identified some companies I'd like to delete from Hubspot, and would like to extend the Company client to support it. I found this documentation, and am putting together a PR now :)
Hi,
I'm struggling to get the update function in the company client to work.
def update(self, company_id: str, data: Dict = None, **options) -> Dict:
"""update the given company with data"""
data = data or {}
return self._call(
"companies/{}".format(company_id), data=data, method="PUT", **options
)
I'm wondering if its asking for data, but the request in the Hubspot API is looking for properties. Would this be causing it not to work? For example, the sample JSON hubspot is showing in the API, https://legacydocs.hubspot.com/docs/methods/companies/update_company, is expecting properties, not data. The sample JSON given, is:
Example PUT JSON:
{
"properties": [
{
"name": "description",
"value": "A far better description than before"
}
]
}
Right now I'm calling the function with:
updateCompanyDict = {
"name":"rpm_id",
"value":rpmCustomerAccountId
}
response = companies_client_update.update(company_id=company.companyId, data=updateCompanyDict, debug=True)
The debug for the request above shows:
{
"data": "{"name": "rpm_id", "value": 1286}",
"headers": {
"Accept-Encoding": "gzip",
"Content-Type": "application/json"
},
"url": "/companies/v2/companies/4326408039?hapikey=hapikey"
}
where hapikey, is our key.
Can you please shed some light?
Hey,
I've recently discovered this package and I thank you for developing it!
I'd like to ask please if there's a plan on supporting the Timeline API?
Thanks!
As documented in the project README (https://github.com/jpetrucciani/hubspot3#passing-params), I should pass params
to send extra properties to hubspot. Seems this doesn't work for contacts:
contacts = client.contacts.get_all(params={"showListMemberships": "true"}, extra_properties=["hs_language"])
I got:
contacts = client.contacts.get_all(params={"showListMemberships": "true"}, extra_properties=["hs_language"])
File "/home/kvdb/.local/share/virtualenvs/send-cMuOhGEa/lib/python3.8/site-packages/hubspot3/contacts.py", line 163, in get_all
batch = self._call(
TypeError: _call() got multiple values for keyword argument 'params'
use plural pipelines
instead of pipeline
in the url of create and update methods
according to the description of hubspot
https://api.hubapi.com/crm-pipelines/v1/pipelines/deals?hapikey=demo
def create(self, object_type, data=None, **options):
data = data or {}
return self._call(
"pipeline/{}".format(object_type), data=data, method="POST", **options
)
def update(self, object_type, key, data=None, **options):
data = data or {}
return self._call(
"pipeline/{}/{}".format(object_type, key),
data=data,
method="PUT",
**options
)
Hi,
After having upgraded to the last version of hubspot3
I find that sometime, when fetching objects from the Hubspot API, i may retrieve dict formatted in different ways regarding objects properties.
Sometimes its the 'hubspot' way of formatting object properties:
{'objectId': 47863442,
'portalId': 5799819,
'isDeleted': False,
'objectType': 'PRODUCT',
'properties': {'name': {'value': "My product name",
'source': 'CRM_UI',
'sourceId': None,
'versions': [{'name': 'name',
'value': "My product name",
'source': 'CRM_UI',
'requestId': 'dcb6af03-4c5e-499a-9448-8c689c4f984a',
'sourceVid': [],
'timestamp': 1570095121725}],
'timestamp': 1570095121725},
'price': {'value': '115',
'source': 'CRM_UI',
'sourceId': None,
'versions': [{'name': 'price',
'value': '115',
'source': 'CRM_UI',
'requestId': 'dcb6af03-4c5e-499a-9448-8c689c4f984a',
'sourceVid': [],
'timestamp': 1570095121725}],
'timestamp': 1570095121725}
}}
and sometimes I only retrieve key / values (not placed under the properties
key):
{
'id': 28612078,
'name': 'My product name',
'price': '115',
}
I think that getting a single product will returns to me an hubspot payload while retrieving all the products will return to me prettified products. It seems to be the same thing for most of clients.
I personally prefer the second format that I find more natural for developers. And it could be great to be able to use this format for both input and outputs.
But what I find disturbing is that not all of the clients methods are returning the object information the same way.
What do you think of that?
Hey,
Is there a way to see what kind of request is send for a call?
The specific reason I am asking is that I ran
deals_client = DealsClient(api_key=API)
deals_client.get_all(propertiesWithHistory="Amount")
I want to see if propertiesWithHistory="Amount"
gets appended at all, documented here.
Now that the use of API KEYS have been depreciated by HS, would you kindly add an example of authenticating using an ACCESS TOKEN to your readme.
Thanks for the recent updates.
Would you accept a PR that allowed users to pass a callback function or object to the Hubspot3 class allowing it to publish metrics? For example a simple counter of HTTP requests and response codes? Something along the lines of
# hubspot3.metrics or somewhere similar
class MetricsClient(Protocol):
def counter(self, name: str, value: int, tags: Optional[dict[str, str | int | float]]):
pass
# hubspot3.base
class BaseClient:
def __init__(..., metrics_client: MetricsClient = None):
self.metrics_client = metrics_client
def _counter(self, name: str, value: int, tags: Optional[dict[str, str | int | float]]):
if not self.metrics_client:
return
try:
self.metrics_client.counter(name, value, tags)
except Exception:
logging.exception("something went wrong publishing a metric")
def _execute_raw_request(self, conn, request):
...
self._counter("endpoint", 1, {"http_status": 123})
return result
# user code
class MyMetricsClient:
def counter(self, name: str, value: int, tags: Optional[dict[str, str | int | float]]):
# user implemented method to publish the metric somewhere
client = Hubspot3(access_token="blah", metrics_client=MyMetricsClient)
My motivation is using the new Private Apps there is no equivalent to /integrations/v1/limit/daily that shows your API usage. I've raised a feature request with HubSpot but I'm not hopeful.
I just wanted to put in an issue for the readme API limits. They were recently updated:
https://developers.hubspot.com/apps/api_guidelines
It's not terribly important, but just wanted to get it on the radar.
Hey, it's me again :)
Does the library support running an API that requires OAuth 2 authentication?
EDIT:
I also mean if for example I took care of the authentication myself, can I use the access token instead of API key to run the commands using this library?
Thanks!
I think we have room for improvements on the exception handling side.
this is one example of output when a server side error happens:
HubspotServerError
Hubspot Error
---- request ----
POST api.hubapi.com/contacts/v1/contact/createOrUpdate/email/[email protected]?, [timeout=<class 'int'>]
---- body ----
<class 'NoneType'>
---- headers ----
<class 'dict'>
---- result ----
<class 'int'>
---- body -----
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
---- headers -----
<class 'http.client.HTTPMessage'>
---- reason ----
Bad Gateway
---- trigger error ----
<class 'NoneType'>
As a solution on such cases we can catch the exception and try to somehow parse the reason, but would be way more convenient if such thing come from the package.
Hello!
First of all: this is a really good library and I appreciate the awesome work.
When I have to do hundreds of requests things start to become a bit slowly - which is completely normal - so I was wondering if you guys have any plan to implement an asynchronous programming here.
Otherwise, could you facilitate my (likely) future contribution by pointing out which parts of the code should I look at first?
Thank you very much!
I am trying to use hubspot3 with this command:
hubspot3 --config config.json crm_associations create --from_object 7307801 --to_object 1181162112 --definition 4
and receive the following error.
AttributeError: 'int' object has no attribute 'value'
I cannot figure it out. Any ideas?
My apologies if this question is super basic and obvious. I'm new to this and after 12 hours I think I need some help.
Hi,
Sorry if I misunderstood, but I get some printouts when running and suspect it is these two print statements doing it:
while not finished:
print(offset)
batch = self._call(
"engagements/associated/{}/{}/paged".format(object_type, object_id),
method="GET",
params={"limit": query_limit, "offset": offset},
**options
)
print(len(batch["results"]))
Seems to have happened since hubspot had their outage
reproduce:
engagements_client = EngagementsClient(api_key=api)
# Handle offset
print("Getting list of all engagements")
engagements = engagements_client.get_all()
output (I checked, that status is indeed 500):
Getting list of all engagements
Traceback (most recent call last):
File "/opt/shapedo/sales/hubspot/get_all_data.py", line 36, in
get_all_engagements(os.path.join(args.folder, 'hubspot_engagements.json'), API)
File "/opt/shapedo/sales/hubspot/engagements.py", line 12, in get_all_engagements
engagements = engagements_client.get_all()
File "/usr/local/lib/python3.6/dist-packages/hubspot3/engagements.py", line 73, in get_all
**options
File "/usr/local/lib/python3.6/dist-packages/hubspot3/base.py", line 361, in _call
**options
File "/usr/local/lib/python3.6/dist-packages/hubspot3/base.py", line 261, in _call_raw
result = self._execute_request_raw(connection, request_info)
File "/usr/local/lib/python3.6/dist-packages/hubspot3/base.py", line 182, in _execute_request_raw
raise HubspotServerError(result, request)
hubspot3.error.HubspotServerError:
internal error
---- request ----
GET api.hubapi.com/engagements/v1/engagements/paged?limit=250&offset=655163296&hapikey=xxxxxx, [timeout= ]
---- body ----
---- headers ----
---- result ----
---- body -----
{"status":"error","message":"internal error","correlationId":"9df8fff4-739f-4e3a-bb2e-b8e539ddf136","requestId":"c69f7fd841bf5bcc064aebbeab45b65f"}
---- headers -----
---- reason ----
Internal Server Error
---- trigger error ----
using hubspot3==3.2.3
Using cached https://files.pythonhosted.org/packages/78/d7/3e3a9ff716b29c07b451bcd200449cdf360b15415f9bd17c162e1b33460c/hubspot3-3.2.11.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/pip-build-mn7fko9u/hubspot3/setup.py", line 5, in <module>
from hubspot3.globals import __version__
File "/tmp/pip-build-mn7fko9u/hubspot3/hubspot3/__init__.py", line 5, in <module>
from hubspot3.error import HubspotBadConfig, HubspotNoConfig
File "/tmp/pip-build-mn7fko9u/hubspot3/hubspot3/error.py", line 6, in <module>
from hubspot3.utils import force_utf8
File "/tmp/pip-build-mn7fko9u/hubspot3/hubspot3/utils.py", line 4, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
Getting this on Elastic Beanstalk, if I pip install requests
separately and then run it all works fine?
Any plans to support this endpoint? It isn't exposed in the EmailEventsClient.
I was experiencing an issue while using the companies client to query for recently modified companies. The client sends the request 250 at a time, per the default limit
parameter and then increments the offset by that amount for the next query. Then when it gets to the upper limit of requested records (10000) it continues and sends another request with a 10250 offset
, which throws a bad request error. Since the finished
boolean value is only ever made to true when batch["hasMore"]
is set to false, even if it has reached the upper limit, then it continues to make requests.
I have no idea why my company has over 10000 modified company records or if hubspot is returning more than just the recently modified records, but it seems to me like the client should stop looping at that upper limit to avoid the error. An additional check at the bottom of the loop should be enough to solve this:
finished = not batch["hasMore"] or offset + limit > 10000
I'd also probably move that line to be after the new offset is set, so we can get the value of what it would be for the next call.
It's also a tough one to get around because limit only limits the size of each call in the loop. I would also suggest adding some sort of way to limit the total number of records returned from a call, which isn't a necessity but would help to work around something like this.
I can probably submit a fix for this if you agree on the solution I proposed or if you have any other ideas I'd love to hear them.
Hi, I noticed there isn't an obvious way (to me at least) to conveniently request additional properties when requesting bulk deals. I came accross this using the DealsClient.get_all()
method. For now, I am getting around this by building a query string manually and passing it to the method as query=&property=property1
.
It seems to me that there should be some way of expanding the requested properties in the method. I could play around with adding this functionality when I have a bit more time.
Of course, if there is a way to do this that I'm completely missing, I'd love to hear about it.
Platform:
IBM Mainframe
Replication steps:
Pls fix rito
How to get data from HubspotConflict
as it's described in HS documentation?
When I get the contact list from hub sport it gets them in a large dataset since the project I'm working on has a lot of contacts like over 2000. How do I paginate this?
@api.route("/hubspot/contacts/")
@cross_origin(supports_credentials=True)
def getHubspotContacts():
"""Gets a list of the clients we have in hubspot"""
API_KEY = "api-key"
hb_client = Hubspot3(api_key=API_KEY)
contacts = hb_client.contacts.get_all()
# display = contacts[range_val:(range_val+20)]
return jsonify(contacts)
File "send_docs.py", line 22, in
from hubspot3 import Hubspot3
File "C:\Users\Ty Cooper\AppData\Local\Programs\Python\Python37\lib\site-packages\hubspot3_init_.py", line 9, in
class Hubspot3UsageLimits:
File "C:\Users\Ty Cooper\AppData\Local\Programs\Python\Python37\lib\site-packages\hubspot3_init_.py", line 21, in Hubspot3UsageLimits
collected_at: datetime = datetime.fromtimestamp(0),
OSError: [Errno 22] Invalid argument
My colleagues and I are currently in the process of extending existing clients by adding more endpoints (mainly contacts) and adding new clients (e.g. Ecommerce Bridge) - PRs will follow. ;-)
When it comes to the Contacts API, one of the endpoints we want to add is the Update a contact by email endpoint (currently, hubspot3
only offers the corresponding endpoint that works with IDs).
To implement this as a new method, we will have to choose a name for it - which is why we found that the methods of the ContactsClient
use multiple different naming conventions:
by_id
/by_email
suffix, e.g. get_contact_by_id
a_contact
suffix, e.g. update_a_contact
update
The first two conventions seem to follow the names from the endpoint overview on the left side of the HubSpot API docs, although the a_contact
suffix is not necessarily nice.
The update_a_contact
and update
methods also seem to contain the same logic and are therefore duplicates, which means that one of them should probably be removed in the long run.
My questions are:
update_a_contact_by_email
to follow the names in the HubSpot API overview or should it be called update_contact_by_email
to follow the convention of the get_...
-methods (while renaming the current update
method to update_contact_by_id
)? Or even something shorter like update_by_email
?update
method and other deprecations? I would generally suggest to change one of the duplicate methods to give a deprecation warning and then simply call the other method (that way, the duplicate code is removed, but both methods remain for implementations that already use them; the method containing the deprecation warning can then be removed in the future after developers had the time to react to the warning). The same could be applied to methods that would potentially need renaming after choosing a naming scheme in the first question. Is there a deprecation policy so we can already include the version number in which such methods would finally be removed in such warnings (we could also just say "will be removed in the future")?We would implement all of this when extending the ContactsClient
and throw a PR your way - we just need to know what the preferred naming convention/deprecation policy would be.
Hello,
I believe this is closely related to ticket #14. I'm trying to pull a number of fields across all contacts: vid, email, and a custom field.
Is there a way to select this within .get_all()? Or is there another way to solve for this?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.