From 48165e6004a2fcefc912f6c0394cbfbcc39b1083 Mon Sep 17 00:00:00 2001 From: Bradley Payne Date: Thu, 18 Dec 2025 09:49:41 -0700 Subject: [PATCH 1/2] feat: enhance API request methods to support return_request_obj parameter. This allows functionality for endpoints that do not return a json body --- ProPyCore/access/base.py | 138 +++++++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 41 deletions(-) diff --git a/ProPyCore/access/base.py b/ProPyCore/access/base.py index 9ae0504..d14213d 100644 --- a/ProPyCore/access/base.py +++ b/ProPyCore/access/base.py @@ -3,11 +3,12 @@ from ..exceptions import raise_exception + class Base: """ Base class for Procore API access """ - + def __init__(self, access_token, server_url) -> None: """ Initializes important API access parameters @@ -19,12 +20,18 @@ def __init__(self, access_token, server_url) -> None: __server_url : str base url to send GET/POST requests """ + self.__access_token = access_token self.__server_url = server_url - def get_request(self, api_url, additional_headers=None, params=None): - """ - Create a HTTP Get request + def get_request( + self, + api_url, + additional_headers=None, + params=None, + return_request_obj: bool = False, + ): + """Create an HTTP GET request. Parameters ---------- @@ -34,12 +41,18 @@ def get_request(self, api_url, additional_headers=None, params=None): additional headers beyond Authorization params : dict, default None GET parameters to parse + return_request_obj : bool, default False + If True, return the underlying ``requests.Response`` object + instead of the parsed JSON / default return type. Returns ------- - response : dict - GET response in json + dict or requests.Response + By default, the GET response in JSON (``response.json()``). + If ``return_request_obj`` is True, returns the raw + ``requests.Response`` object instead. """ + if params is None: url = self.__server_url + api_url else: @@ -51,15 +64,22 @@ def get_request(self, api_url, additional_headers=None, params=None): headers[key] = value response = requests.get(url, headers=headers) - + if response.ok: - return response.json() + return response if return_request_obj else response.json() else: raise_exception(response) - - def post_request(self, api_url, additional_headers=None, params=None, data=None, files=None): - """ - Create a HTTP Post request + + def post_request( + self, + api_url, + additional_headers=None, + params=None, + data=None, + files=None, + return_request_obj: bool = False, + ): + """Create an HTTP POST request. Parameters ---------- @@ -67,16 +87,24 @@ def post_request(self, api_url, additional_headers=None, params=None, data=None, endpoint for the specific API call additional_headers : dict, default None additional headers beyond Authorization + params : dict, default None + Query parameters for the POST request data : dict, default None POST data to send files : list of tuple, default None open files to send to Procore + return_request_obj : bool, default False + If True, return the underlying ``requests.Response`` object + instead of the parsed JSON / default return type. Returns ------- - response : HTTP response object - POST response details in json + dict or requests.Response + By default, the POST response in JSON (``response.json()``). + If ``return_request_obj`` is True, returns the raw + ``requests.Response`` object instead. """ + # Get URL if params is None: url = self.__server_url + api_url @@ -96,35 +124,42 @@ def post_request(self, api_url, additional_headers=None, params=None, data=None, "POST", url, headers=headers, - json=data # Use json parameter instead of data to properly serialize + json=data, # Use json parameter instead of data to properly serialize ) - ''' + """ print(f"Request URL: {response.request.url}") print(f"Request Headers: {response.request.headers}") print(f"Request Data: {response.request.body}") - ''' + """ elif data is None: response = requests.request( "POST", url, headers=headers, - files=files # use files for multipart/form-data + files=files, # use files for multipart/form-data ) else: response = requests.request("POST", url, headers=headers, data=data, files=files) if response.ok: - return response.json() + return response if return_request_obj else response.json() else: - ''' + """ print("Response Status Code:", response.status_code) print("Response Text:", response.text) - ''' + """ raise_exception(response) - def patch_request(self, api_url, additional_headers=None, params=None, data=None, files=False): - """ - Create a HTTP PATCH request + def patch_request( + self, + api_url, + additional_headers=None, + params=None, + data=None, + files=False, + return_request_obj: bool = False, + ): + """Create an HTTP PATCH request. Parameters ---------- @@ -135,17 +170,23 @@ def patch_request(self, api_url, additional_headers=None, params=None, data=None params : dict, default None PATCH parameters to parse data : dict, default None - POST data to send + PATCH data to send files : dict or boolean, default False - False - updating folder so use json request + False - updating folder so use JSON request True - updating file, but no file to include dict - updating file with new document + return_request_obj : bool, default False + If True, return the underlying ``requests.Response`` object + instead of the parsed JSON / default return type. Returns ------- - response : HTTP response object - PATCH response details in json + dict or requests.Response + By default, the PATCH response in JSON (``response.json()``). + If ``return_request_obj`` is True, returns the raw + ``requests.Response`` object instead. """ + # Get URL if params is None: url = self.__server_url + api_url @@ -157,35 +198,41 @@ def patch_request(self, api_url, additional_headers=None, params=None, data=None if additional_headers is not None: for key, value in additional_headers.items(): headers[key] = value - + if files is False: response = requests.patch( url, headers=headers, - json=data # json for folder update + json=data, # json for folder update ) elif files is True: response = requests.patch( url, headers=headers, - data=data, # data for file update + data=data, # data for file update ) else: response = requests.patch( url, headers=headers, - data=data, # data for file update - files=files + data=data, # data for file update + files=files, ) if response.ok: - return response.json() + return response if return_request_obj else response.json() else: raise_exception(response) - - def delete_request(self, api_url, additional_headers=None, params=None): + + def delete_request( + self, + api_url, + additional_headers=None, + params=None, + return_request_obj: bool = False, + ): """ - Execute a HTTP DELETE request + Execute an HTTP DELETE request. Parameters ---------- @@ -195,12 +242,19 @@ def delete_request(self, api_url, additional_headers=None, params=None): additional headers beyond Authorization params : dict, default None DELETE parameters to parse + return_request_obj : bool, default False + If True, return the underlying ``requests.Response`` object + instead of the default status-code dict. Returns ------- - response : HTTP response object - DELETE response details in json + dict or requests.Response + By default, a dict containing the status code, + ``{"status_code": response.status_code}``. + If ``return_request_obj`` is True, returns the raw + ``requests.Response`` object instead. """ + # Get URL if params is None: url = self.__server_url + api_url @@ -220,6 +274,8 @@ def delete_request(self, api_url, additional_headers=None, params=None): ) if response.ok: - return {"status_code":response.status_code} + if return_request_obj: + return response + return {"status_code": response.status_code} else: - raise_exception(response) \ No newline at end of file + raise_exception(response) From 3772cfc940b9bf4f6b7684a2297a108a34375d7e Mon Sep 17 00:00:00 2001 From: Bradley Payne Date: Thu, 18 Dec 2025 11:10:03 -0700 Subject: [PATCH 2/2] feat: add base_api object to main class to allow users to call any api from the main connection class --- ProPyCore/procore.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProPyCore/procore.py b/ProPyCore/procore.py index 500173c..9ff44b5 100644 --- a/ProPyCore/procore.py +++ b/ProPyCore/procore.py @@ -1,5 +1,5 @@ from .exceptions import * -from .access import companies, generic_tools, projects, documents, rfis, directory, submittals, tasks, budgets, direct_costs, cost_codes, time, quality, photos, permissions, change_events, drawings +from .access import base, companies, generic_tools, projects, documents, rfis, directory, submittals, tasks, budgets, direct_costs, cost_codes, time, quality, photos, permissions, change_events, drawings import requests class Procore: @@ -44,6 +44,7 @@ def __init__(self, client_id, client_secret, redirect_uri, base_url, oauth_url) def _init_endpoints(self): # General + self.base_api = base.Base(access_token=self.__access_token, server_url=self.__base_url) self.companies = companies.Companies(access_token=self.__access_token, server_url=self.__base_url) self.projects = projects.Projects(access_token=self.__access_token, server_url=self.__base_url) self.permissions = permissions.Permissions(access_token=self.__access_token, server_url=self.__base_url)