From 3729d795070009893f7d2027bdc878e67f761f0c Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Thu, 14 Dec 2023 16:15:38 -0300 Subject: [PATCH 01/14] Support for Pipedrive Projects --- pipedrive/client.py | 2 ++ pipedrive/projects.py | 68 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 pipedrive/projects.py diff --git a/pipedrive/client.py b/pipedrive/client.py index 856db91..ae532bf 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -5,6 +5,7 @@ from pipedrive import exceptions from pipedrive.activities import Activities from pipedrive.deals import Deals +from pipedrive.projects import Projects from pipedrive.filters import Filters from pipedrive.leads import Leads from pipedrive.items import Items @@ -31,6 +32,7 @@ def __init__(self, client_id=None, client_secret=None, domain=None): self.api_token = None self.activities = Activities(self) self.deals = Deals(self) + self.projects = Projects(self) self.filters = Filters(self) self.leads = Leads(self) self.items = Items(self) diff --git a/pipedrive/projects.py b/pipedrive/projects.py new file mode 100644 index 0000000..5ae77e5 --- /dev/null +++ b/pipedrive/projects.py @@ -0,0 +1,68 @@ +class Projects(object): + def __init__(self, client): + self._client = client + + def get_project(self, project_id, **kwargs): + url = "projects/{}".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_all_projects(self, params=None, **kwargs): + url = "projects" + return self._client._get(self._client.BASE_URL + url, params=params, **kwargs) + + def get_all_projects_with_filter(self, filter_id, params=None, **kwargs): + url = "projects?filter_id={}".format(filter_id) + return self._client._get(self._client.BASE_URL + url, params=params, **kwargs) + + def create_project(self, data, **kwargs): + url = "projects" + return self._client._post(self._client.BASE_URL + url, json=data, **kwargs) + + def update_project(self, project_id, data, **kwargs): + url = "projects/{}".format(project_id) + return self._client._put(self._client.BASE_URL + url, json=data, **kwargs) + + def delete_project(self, project_id, **kwargs): + url = "projects/{}".format(project_id) + return self._client._delete(self._client.BASE_URL + url, **kwargs) + + def duplicate_project(self, project_id, **kwargs): + url = "projects/{}/duplicate".format(project_id) + return self._client._post(self._client.BASE_URL + url, **kwargs) + + def get_project_details(self, project_id, **kwargs): + url = "projects/{}".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def search_projects(self, params=None, **kwargs): + url = "projects/search" + return self._client._get(self._client.BASE_URL + url, params=params, **kwargs) + + def get_project_plan(self, project_id, **kwargs): + url = "projects/{}/plan".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_project_groups(self, project_id, **kwargs): + url = "projects/{}/groups".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_project_tasks(self, project_id, **kwargs): + url = "projects/{}/tasks".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_project_activities(self, project_id, **kwargs): + url = "projects/{}/activities".format(project_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_project_fields(self, params=None, **kwargs): + url = "projectFields" + return self._client._get(self._client.BASE_URL + url, params=params, **kwargs) + + def get_all_projects_boards(self, params=None, **kwargs): + url = "projects/boards" + return self._client._get(self._client.BASE_URL + url, params=params, **kwargs) + + def get_board(self, board_id, **kwargs): + url = "projects/boards/{}".format(board_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + From fb50d1f85909d197a264824227739c874a8f46ca Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Fri, 15 Dec 2023 10:46:06 -0300 Subject: [PATCH 02/14] Append API version to domain-less API urls --- pipedrive/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pipedrive/client.py b/pipedrive/client.py index ae532bf..a187f57 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -51,6 +51,9 @@ def __init__(self, client_id=None, client_secret=None, domain=None): if not domain.endswith("/"): domain += "/" self.BASE_URL = domain + "v1/" + else: + self.BASE_URL = "v1/" + def authorization_url(self, redirect_uri, state=None): params = { From 88cc63766f13b2326d8478a5e3b39092b38d83dc Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Fri, 15 Dec 2023 10:48:21 -0300 Subject: [PATCH 03/14] Append API version to domain-less API urls --- pipedrive/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipedrive/client.py b/pipedrive/client.py index a187f57..4c8f7c7 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -52,7 +52,7 @@ def __init__(self, client_id=None, client_secret=None, domain=None): domain += "/" self.BASE_URL = domain + "v1/" else: - self.BASE_URL = "v1/" + self.BASE_URL = self.BASE_URL + "v1/" def authorization_url(self, redirect_uri, state=None): From ed363ef07cbcc4b0313325677d144a655cd3b40d Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Fri, 15 Dec 2023 10:51:51 -0300 Subject: [PATCH 04/14] bumped version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0eab7cd..f9f87be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pipedrive-python-lib" -version = "1.2.3" +version = "1.2.4" description = "API wrapper for Pipedrive written in Python" authors = ["Miguel Ferrer "] license = "MIT" From e5ae05abbe735406defe5e7b9ddc601083ae5d7b Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Fri, 15 Dec 2023 11:27:32 -0300 Subject: [PATCH 05/14] support for board phases --- pipedrive/projects.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pipedrive/projects.py b/pipedrive/projects.py index 5ae77e5..8d2e9e7 100644 --- a/pipedrive/projects.py +++ b/pipedrive/projects.py @@ -66,3 +66,12 @@ def get_board(self, board_id, **kwargs): url = "projects/boards/{}".format(board_id) return self._client._get(self._client.BASE_URL + url, **kwargs) + def get_project_phases(self, board_id, **kwargs): + url = "projects/phases?board_id={}".format(board_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + def get_phase(self, phase_id, **kwargs): + url = "projects/phases/{}".format(phase_id) + return self._client._get(self._client.BASE_URL + url, **kwargs) + + From 21a914e4fc627d4fd99a25af8338869b0d99a1ed Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Fri, 15 Dec 2023 11:27:47 -0300 Subject: [PATCH 06/14] bumped version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9f87be..8f12aef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pipedrive-python-lib" -version = "1.2.4" +version = "1.2.5" description = "API wrapper for Pipedrive written in Python" authors = ["Miguel Ferrer "] license = "MIT" From 25b353b37689f0841aaee4e155a073e5459215f1 Mon Sep 17 00:00:00 2001 From: martin sarsale Date: Mon, 18 Dec 2023 15:07:15 -0300 Subject: [PATCH 07/14] downgrade version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f12aef..0eab7cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pipedrive-python-lib" -version = "1.2.5" +version = "1.2.3" description = "API wrapper for Pipedrive written in Python" authors = ["Miguel Ferrer "] license = "MIT" From 07653c0157562d71db9fd4fc4910b9a958c1fe21 Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Thu, 30 May 2024 09:45:18 -0300 Subject: [PATCH 08/14] Adds retries - code from https://github.com/GearPlug/pipedrive-python/pull/32/files --- pipedrive/client.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pipedrive/client.py b/pipedrive/client.py index 4c8f7c7..3de02e6 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -121,7 +121,34 @@ def _request(self, method, url, headers=None, params=None, **kwargs): _headers.update(headers) if params: _params.update(params) - return self._parse(requests.request(method, url, headers=_headers, params=_params, **kwargs)) + + number_of_retries = kwargs.get('number_of_retries', 3) + intervaltime = kwargs.get('intervaltime', 500) + + # remove number of retries and intervaltime from kwargs, otherwise the requests call will fail. + if 'number_of_retries' in kwargs: + del kwargs['number_of_retries'] + if 'intervaltime' in kwargs: + del kwargs['intervaltime'] + + if number_of_retries: + while number_of_retries > 0: + try: + response = self._parse(requests.request(method, url, headers=_headers, params=_params, **kwargs)) + # No except, response is ok, return it. + return response + except (exceptions.BadRequestError, exceptions.UnauthorizedError, exceptions.NotFoundError, + exceptions.UnsupportedMediaTypeError, exceptions.UnprocessableEntityError, + exceptions.NotImplementedError, exceptions.TooManyRequestsError) as e: + # Do not retry, just return the response. + raise e + except (exceptions.ForbiddenError, exceptions.InternalServerError, exceptions.ServiceUnavailableError, + exceptions.UnknownError): + # Retry! There is hope. + number_of_retries -= 1 + time.sleep(intervaltime / 1000.0) + else: + return self._parse(requests.request(method, url, headers=_headers, params=_params, **kwargs)) def _parse(self, response): status_code = response.status_code From 78e8a19e6338389b306ba5fdec9f87ab7ce1fe6c Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Fri, 31 May 2024 11:02:26 -0300 Subject: [PATCH 09/14] Raise retry delay --- pipedrive/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipedrive/client.py b/pipedrive/client.py index 3de02e6..b4c863f 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -122,8 +122,8 @@ def _request(self, method, url, headers=None, params=None, **kwargs): if params: _params.update(params) - number_of_retries = kwargs.get('number_of_retries', 3) - intervaltime = kwargs.get('intervaltime', 500) + number_of_retries = kwargs.get('number_of_retries', 5) + intervaltime = kwargs.get('intervaltime', 2500) # remove number of retries and intervaltime from kwargs, otherwise the requests call will fail. if 'number_of_retries' in kwargs: From 6ea0dc818fdb1b19f5ecc87c4f0dcc69227fbd4f Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Thu, 6 Jun 2024 09:27:58 -0300 Subject: [PATCH 10/14] feat: import time module for retry delay in client.py --- pipedrive/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pipedrive/client.py b/pipedrive/client.py index b4c863f..d78a05d 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -1,3 +1,4 @@ +import time from urllib.parse import urlencode import requests From b319fdcf424d2159765fe4fa96ab69bfdf6fd848 Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Mon, 24 Jun 2024 14:12:51 -0300 Subject: [PATCH 11/14] Retry exceptions.TooManyRequestsError --- pipedrive/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipedrive/client.py b/pipedrive/client.py index d78a05d..0e54405 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -140,11 +140,11 @@ def _request(self, method, url, headers=None, params=None, **kwargs): return response except (exceptions.BadRequestError, exceptions.UnauthorizedError, exceptions.NotFoundError, exceptions.UnsupportedMediaTypeError, exceptions.UnprocessableEntityError, - exceptions.NotImplementedError, exceptions.TooManyRequestsError) as e: + exceptions.NotImplementedError) as e: # Do not retry, just return the response. raise e except (exceptions.ForbiddenError, exceptions.InternalServerError, exceptions.ServiceUnavailableError, - exceptions.UnknownError): + exceptions.UnknownError, exceptions.TooManyRequestsError): # Retry! There is hope. number_of_retries -= 1 time.sleep(intervaltime / 1000.0) From e69c64e035372cce5c587259a219e43dfb916880 Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Mon, 24 Jun 2024 14:13:08 -0300 Subject: [PATCH 12/14] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0eab7cd..f9f87be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pipedrive-python-lib" -version = "1.2.3" +version = "1.2.4" description = "API wrapper for Pipedrive written in Python" authors = ["Miguel Ferrer "] license = "MIT" From 739480cad650d35e679ba11632ad17e06afad8ca Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Fri, 13 Sep 2024 15:48:52 -0300 Subject: [PATCH 13/14] Use Request Sessions so urllib3 connectionpools are used --- pipedrive/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipedrive/client.py b/pipedrive/client.py index 0e54405..0a0ef16 100644 --- a/pipedrive/client.py +++ b/pipedrive/client.py @@ -47,6 +47,7 @@ def __init__(self, client_id=None, client_secret=None, domain=None): self.stages = Stages(self) self.users = Users(self) self.webhooks = Webhooks(self) + self.requests_session = requests.Session() if domain: if not domain.endswith("/"): @@ -135,7 +136,7 @@ def _request(self, method, url, headers=None, params=None, **kwargs): if number_of_retries: while number_of_retries > 0: try: - response = self._parse(requests.request(method, url, headers=_headers, params=_params, **kwargs)) + response = self._parse(self.requests_session.request(method, url, headers=_headers, params=_params, **kwargs)) # No except, response is ok, return it. return response except (exceptions.BadRequestError, exceptions.UnauthorizedError, exceptions.NotFoundError, @@ -149,7 +150,7 @@ def _request(self, method, url, headers=None, params=None, **kwargs): number_of_retries -= 1 time.sleep(intervaltime / 1000.0) else: - return self._parse(requests.request(method, url, headers=_headers, params=_params, **kwargs)) + return self._parse(self.requests_session.request(method, url, headers=_headers, params=_params, **kwargs)) def _parse(self, response): status_code = response.status_code From 5a4f540af95929c73c8b0483768ebd00a6d19320 Mon Sep 17 00:00:00 2001 From: Martin Sarsale Date: Fri, 13 Sep 2024 15:49:14 -0300 Subject: [PATCH 14/14] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9f87be..8f12aef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pipedrive-python-lib" -version = "1.2.4" +version = "1.2.5" description = "API wrapper for Pipedrive written in Python" authors = ["Miguel Ferrer "] license = "MIT"