From ab06003f1af913457616d3e25c8bc86c688b46e3 Mon Sep 17 00:00:00 2001 From: Mark Ciecior Date: Mon, 6 Oct 2025 16:00:46 -0500 Subject: [PATCH 1/4] add doc_download feature --- connectpyse/cw_controller.py | 6 ++++++ connectpyse/system/document download.py | 10 ++++++++++ connectpyse/system/document_download_api.py | 14 ++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 connectpyse/system/document download.py create mode 100644 connectpyse/system/document_download_api.py diff --git a/connectpyse/cw_controller.py b/connectpyse/cw_controller.py index 363904c..9f07021 100644 --- a/connectpyse/cw_controller.py +++ b/connectpyse/cw_controller.py @@ -1,4 +1,5 @@ # Parent class for module controller classes +from io import BytesIO from .config import API_URL, basic_auth, ENSURE_ASCII from .restapi import Client @@ -32,6 +33,11 @@ def _get(self): for json in json_results: yield self._class(json) + def _get_bytes(self): + results = getattr(self, self.module).get(user_headers=self.basic_auth, + user_params=self._format_user_params()) + return self._class(results.content) + def _create(self, a_object): # Ideally take the_item and submit that as the user_data try: diff --git a/connectpyse/system/document download.py b/connectpyse/system/document download.py new file mode 100644 index 0000000..0aee116 --- /dev/null +++ b/connectpyse/system/document download.py @@ -0,0 +1,10 @@ +from ..cw_model import CWModel + + +class DocumentDownload(CWModel): + + def __init__(self, bytes=None): + self.bytes = None # (BytesIO) + + # initialize object with bytes + super().__init__(bytes) diff --git a/connectpyse/system/document_download_api.py b/connectpyse/system/document_download_api.py new file mode 100644 index 0000000..fe5c68b --- /dev/null +++ b/connectpyse/system/document_download_api.py @@ -0,0 +1,14 @@ +from ..cw_controller import CWController +# Class for /system/document/:id/download +from . import document_download + + +class DocumentDownloadAPI(CWController): + def __init__(self, parent, **kwargs): + self.module_url = 'system' + self.module = 'documents/{}/download'.format(parent) + self._class = document_download.DocumentDownload + super().__init__(**kwargs) # instance gets passed to parent object + + def get_opportunity_team(self): + return super()._get_bytes() From 8e038726108c3f3c5a184d75734b1d08aef703c2 Mon Sep 17 00:00:00 2001 From: Mark Ciecior Date: Mon, 6 Oct 2025 16:03:00 -0500 Subject: [PATCH 2/4] version bump --- connectpyse/CHANGES.md | 10 +++++++++- setup.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/connectpyse/CHANGES.md b/connectpyse/CHANGES.md index e254215..9579866 100644 --- a/connectpyse/CHANGES.md +++ b/connectpyse/CHANGES.md @@ -63,4 +63,12 @@ 0.7.1 --- -- Added support for Opportunity Teams \ No newline at end of file +- Added support for Opportunity Teams + +0.7.2 +--- +- Added support for gathering object _info + +0.7.5 +--- +- Added support for Document Download \ No newline at end of file diff --git a/setup.py b/setup.py index 8575b58..3019a5a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ long_description = f.read() setup(name='connectpyse', - version='0.7.3', + version='0.7.5', description='A ConnectWise API tool for the rest of us.', long_description=long_description, long_description_content_type="text/markdown", From 5b2b2a46a12e8cc484f7b60cf49d01c488a6cff9 Mon Sep 17 00:00:00 2001 From: Mark Ciecior Date: Mon, 6 Oct 2025 16:09:43 -0500 Subject: [PATCH 3/4] bugs --- .gitignore | 1 + .../system/{document download.py => document_download.py} | 0 connectpyse/system/document_download_api.py | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename connectpyse/system/{document download.py => document_download.py} (100%) diff --git a/.gitignore b/.gitignore index 6b2f387..9dc22d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__/ .venv/ +build/ dist/ connectpyse.egg-info/ diff --git a/connectpyse/system/document download.py b/connectpyse/system/document_download.py similarity index 100% rename from connectpyse/system/document download.py rename to connectpyse/system/document_download.py diff --git a/connectpyse/system/document_download_api.py b/connectpyse/system/document_download_api.py index fe5c68b..c374e78 100644 --- a/connectpyse/system/document_download_api.py +++ b/connectpyse/system/document_download_api.py @@ -10,5 +10,5 @@ def __init__(self, parent, **kwargs): self._class = document_download.DocumentDownload super().__init__(**kwargs) # instance gets passed to parent object - def get_opportunity_team(self): + def download_document(self): return super()._get_bytes() From 6c10b571dede49ee5c36c02b101e4a213b5e36c9 Mon Sep 17 00:00:00 2001 From: Mark Ciecior Date: Mon, 6 Oct 2025 16:39:31 -0500 Subject: [PATCH 4/4] working doc download --- connectpyse/README.md | 17 +++++++++++++++++ connectpyse/cw_controller.py | 4 ++-- connectpyse/system/document_download.py | 4 ++-- connectpyse/system/document_download_api.py | 8 ++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/connectpyse/README.md b/connectpyse/README.md index 10516f4..1ec36d4 100644 --- a/connectpyse/README.md +++ b/connectpyse/README.md @@ -69,6 +69,23 @@ their appropriate sections. Import the API class(es) you want to leverage and th >>> print(doc.title) >>> +### To download the documents attached to an opportunity: + + >>> from connectpyse.system import document_api + >>> from connectpyse.system import document_download_api + >>> from connectpyse.sales import opportunity_api + >>> o = opportunity_api.OpportunityAPI(url=URL, auth=AUTH) + >>> d = document_api.DocumentAPI(url=URL, auth=AUTH) + + >>> a_opp = o.get_opportunity_by_id(1234) + >>> myDocs = d.get_documents(a_pp) + >>> for doc in myDocs: + >>> doc_download = document_download_api.DocumentDownloadAPI(doc.id, url=URL, auth=AUTH) + >>> ## Option 1: Get the BytesIO object + >>> bytes = doc_download.download_document() + >>> ## Option 2: Download directly to disk + >>> doc_download.save_to_file("/path/to/file.txt") + ### For example to get a Member's office phone number you would: >>> from connectpyse.system import members_api diff --git a/connectpyse/cw_controller.py b/connectpyse/cw_controller.py index ae37f59..4527465 100644 --- a/connectpyse/cw_controller.py +++ b/connectpyse/cw_controller.py @@ -35,9 +35,9 @@ def _get(self): yield self._class(json) def _get_bytes(self): - results = getattr(self, self.module).get(user_headers=self.basic_auth, + results_str = getattr(self, self.module).get(user_headers=self.basic_auth, user_params=self._format_user_params()) - return self._class(results.content) + return self._class({"bytes": BytesIO(results_str.encode("iso-8859-1"))}) def _create(self, a_object): # Ideally take the_item and submit that as the user_data diff --git a/connectpyse/system/document_download.py b/connectpyse/system/document_download.py index 0aee116..82799ca 100644 --- a/connectpyse/system/document_download.py +++ b/connectpyse/system/document_download.py @@ -3,8 +3,8 @@ class DocumentDownload(CWModel): - def __init__(self, bytes=None): + def __init__(self, json_dict=None): self.bytes = None # (BytesIO) # initialize object with bytes - super().__init__(bytes) + super().__init__(json_dict) diff --git a/connectpyse/system/document_download_api.py b/connectpyse/system/document_download_api.py index c374e78..89b894a 100644 --- a/connectpyse/system/document_download_api.py +++ b/connectpyse/system/document_download_api.py @@ -12,3 +12,11 @@ def __init__(self, parent, **kwargs): def download_document(self): return super()._get_bytes() + + def save_to_file(self, path=""): + document = self.download_document() + if path == "": + raise ValueError("A valid path is required to save the document.") + with open(path, 'wb') as f: + f.write(document.bytes.getvalue()) + return True \ No newline at end of file