pyprocore 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. pyprocore-1.0.2/LICENSE +3 -0
  2. pyprocore-1.0.2/PKG-INFO +371 -0
  3. pyprocore-1.0.2/README.md +335 -0
  4. pyprocore-1.0.2/pyprocore/__init__.py +3 -0
  5. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/app.py +4 -8
  6. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/auth/__init__.py +5 -9
  7. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/auth/oauth.py +5 -11
  8. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/auth/token_manager.py +3 -3
  9. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/auth/token_store.py +8 -5
  10. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/__init__.py +4 -4
  11. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/client.py +5 -7
  12. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/config.py +1 -1
  13. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/logger.py +3 -9
  14. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/models/__init__.py +3 -3
  15. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/models/resources.py +1 -1
  16. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/parser/__init__.py +1 -1
  17. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/parser/email_parser.py +4 -9
  18. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/__init__.py +5 -5
  19. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/companies.py +4 -6
  20. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/files.py +9 -19
  21. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/projects.py +6 -8
  22. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/rfis.py +7 -11
  23. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/services/submittals.py +10 -22
  24. pyprocore-1.0.2/pyprocore.egg-info/PKG-INFO +371 -0
  25. pyprocore-1.0.2/pyprocore.egg-info/SOURCES.txt +39 -0
  26. pyprocore-1.0.2/pyprocore.egg-info/entry_points.txt +2 -0
  27. pyprocore-1.0.2/pyprocore.egg-info/top_level.txt +1 -0
  28. pyprocore-1.0.2/pyproject.toml +86 -0
  29. pyprocore-1.0.2/tests/test_app.py +192 -0
  30. {pyprocore-1.0.0 → pyprocore-1.0.2}/tests/test_client.py +6 -12
  31. {pyprocore-1.0.0 → pyprocore-1.0.2}/tests/test_endpoints.py +1 -1
  32. {pyprocore-1.0.0 → pyprocore-1.0.2}/tests/test_files.py +7 -17
  33. {pyprocore-1.0.0 → pyprocore-1.0.2}/tests/test_services.py +17 -25
  34. pyprocore-1.0.2/tests/test_support_modules.py +336 -0
  35. {pyprocore-1.0.0 → pyprocore-1.0.2}/tests/test_token_management.py +8 -16
  36. pyprocore-1.0.0/PKG-INFO +0 -241
  37. pyprocore-1.0.0/README.md +0 -221
  38. pyprocore-1.0.0/pyprocore.egg-info/PKG-INFO +0 -241
  39. pyprocore-1.0.0/pyprocore.egg-info/SOURCES.txt +0 -35
  40. pyprocore-1.0.0/pyprocore.egg-info/entry_points.txt +0 -2
  41. pyprocore-1.0.0/pyprocore.egg-info/top_level.txt +0 -6
  42. pyprocore-1.0.0/pyproject.toml +0 -62
  43. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/endpoints.py +0 -0
  44. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/core/exceptions.py +0 -0
  45. {pyprocore-1.0.0 → pyprocore-1.0.2/pyprocore}/models/base.py +0 -0
  46. {pyprocore-1.0.0 → pyprocore-1.0.2}/pyprocore.egg-info/dependency_links.txt +0 -0
  47. {pyprocore-1.0.0 → pyprocore-1.0.2}/pyprocore.egg-info/requires.txt +0 -0
  48. {pyprocore-1.0.0 → pyprocore-1.0.2}/setup.cfg +0 -0
@@ -0,0 +1,3 @@
1
+ License placeholder.
2
+
3
+ Replace this file with the approved internal or open-source license before publishing.
@@ -0,0 +1,371 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyprocore
3
+ Version: 1.0.2
4
+ Summary: A production-ready Python SDK for the Procore REST API.
5
+ Author-email: Vibhanshu Mishra <vibhanshumishra72@gmail.com>
6
+ License: License placeholder.
7
+
8
+ Replace this file with the approved internal or open-source license before publishing.
9
+
10
+ Project-URL: Homepage, https://github.com/vibhanshu-mishra/pyprocore
11
+ Project-URL: Repository, https://github.com/vibhanshu-mishra/pyprocore
12
+ Project-URL: Issues, https://github.com/vibhanshu-mishra/pyprocore/issues
13
+ Project-URL: Documentation, https://github.com/vibhanshu-mishra/pyprocore#readme
14
+ Keywords: procore,construction,sdk,python,automation,oauth,api,rfi,submittals
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests>=2.32.3
25
+ Requires-Dist: python-dotenv>=1.0.1
26
+ Requires-Dist: pydantic>=2.8.2
27
+ Requires-Dist: tenacity>=8.5.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: black>=24.10.0; extra == "dev"
30
+ Requires-Dist: coverage>=7.6.0; extra == "dev"
31
+ Requires-Dist: flake8>=7.1.0; extra == "dev"
32
+ Requires-Dist: isort>=5.13.2; extra == "dev"
33
+ Requires-Dist: mypy>=1.13.0; extra == "dev"
34
+ Requires-Dist: types-requests>=2.32.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # PyProcore
38
+
39
+ > A production-ready Python SDK for building automation, integrations, and AI workflows on top of the Procore REST API.
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/pyprocore.svg)](https://pypi.org/project/pyprocore/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/pyprocore.svg)](https://pypi.org/project/pyprocore/)
43
+ [![License](https://img.shields.io/pypi/l/pyprocore.svg)](LICENSE)
44
+ [![Tests](https://github.com/vibhanshu-mishra/pyprocore/actions/workflows/tests.yml/badge.svg)](https://github.com/vibhanshu-mishra/pyprocore/actions/workflows/tests.yml)
45
+ [![Coverage](https://img.shields.io/badge/coverage-94%25-brightgreen.svg)](#tests)
46
+
47
+ PyProcore handles the parts of a Procore integration that are tedious and easy to get wrong — OAuth, token refresh, pagination, retries, typed responses, structured logging, and attachment downloads — so you work with Python objects instead of raw JSON and API plumbing.
48
+
49
+ ---
50
+
51
+ ## Why PyProcore
52
+
53
+ Calling the Procore REST API directly means managing the OAuth handshake, refreshing expired tokens, following pagination headers, retrying failed requests, and parsing untyped JSON on every call.
54
+
55
+ PyProcore does that once, correctly, behind a clean interface. You call a service method and get back a typed Pydantic object. It is designed as the foundation layer for higher-level tools built on Procore data: engineering assistants, document analysis, workflow automation, and AI-powered review.
56
+
57
+ ---
58
+
59
+ ## Features
60
+
61
+ **Authentication and transport**
62
+
63
+ - OAuth 2.0 authorization-code flow
64
+ - Automatic access-token refresh
65
+ - Automatic pagination via Procore response headers
66
+ - Request retries and structured logging with secret redaction
67
+
68
+ **API coverage (v1.0)**
69
+
70
+ - Companies
71
+ - Projects
72
+ - RFIs
73
+ - Submittals
74
+ - Attachment downloads
75
+
76
+ **Developer experience**
77
+
78
+ - Typed Pydantic response models
79
+ - Command-line interface
80
+ - 73 unit tests at 94% coverage, mocked with no live Procore dependency
81
+
82
+ ---
83
+
84
+ ## Architecture
85
+
86
+ | Package | Responsibility |
87
+ | --------------------- | --------------------------------------------------------------- |
88
+ | `pyprocore/auth/` | OAuth exchange, token persistence, token refresh |
89
+ | `pyprocore/core/` | Configuration, endpoint paths, HTTP client, logging, exceptions |
90
+ | `pyprocore/models/` | Pydantic response models |
91
+ | `pyprocore/services/` | Company, project, RFI, submittal, and file services |
92
+ | `pyprocore/parser/` | Email parsing utilities for future automation |
93
+ | `tests/` | Mocked unit tests with no live Procore dependency |
94
+
95
+ ---
96
+
97
+ ## Installation
98
+
99
+ Requires Python 3.12+.
100
+
101
+ ```bash
102
+ pip3 install pyprocore
103
+ ```
104
+
105
+ For local development:
106
+
107
+ ```bash
108
+ git clone https://github.com/vibhanshu-mishra/pyprocore.git
109
+ cd pyprocore
110
+ python3 -m venv .venv
111
+ .venv/bin/python -m pip install --upgrade pip
112
+ .venv/bin/python -m pip install -e .
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Quick Example
118
+
119
+ ```python
120
+ from pyprocore.services import list_projects
121
+
122
+ projects = list_projects(company_id=123456)
123
+
124
+ for project in projects:
125
+ print(project.name)
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Configuration
131
+
132
+ Copy the example file and fill in real values:
133
+
134
+ ```bash
135
+ cp .env.example .env
136
+ ```
137
+
138
+ Required variables:
139
+
140
+ ```bash
141
+ PROCORE_CLIENT_ID=your_client_id
142
+ PROCORE_CLIENT_SECRET=your_client_secret
143
+ PROCORE_REDIRECT_URI=http://localhost:8080/callback
144
+ PROCORE_LOGIN_URL=https://login.procore.com
145
+ PROCORE_API_BASE=https://api.procore.com
146
+ PROCORE_COMPANY_ID=123456
147
+ ```
148
+
149
+ Secrets, tokens, URLs, and company IDs are never hardcoded in source.
150
+
151
+ ---
152
+
153
+ ## Authentication
154
+
155
+ Exchange the first authorization code and save the token locally:
156
+
157
+ ```python
158
+ from pyprocore.auth.oauth import exchange_authorization_code
159
+ from pyprocore.auth.token_manager import TokenManager
160
+
161
+ token_response = exchange_authorization_code("authorization-code-from-procore")
162
+ TokenManager().save_oauth_response(token_response)
163
+ ```
164
+
165
+ After that, SDK clients read the token automatically:
166
+
167
+ ```python
168
+ from pyprocore.auth.token_manager import get_access_token
169
+
170
+ access_token = get_access_token()
171
+ ```
172
+
173
+ Expired access tokens refresh automatically whenever a refresh token is available.
174
+
175
+ ---
176
+
177
+ ## Usage
178
+
179
+ Once `.env` is configured and the one-time OAuth exchange is complete, calls return typed objects:
180
+
181
+ ```python
182
+ from pyprocore.services import list_projects
183
+
184
+ for project in list_projects(company_id=123456):
185
+ print(project.name)
186
+ ```
187
+
188
+ Full service surface:
189
+
190
+ ```python
191
+ from pyprocore.services import (
192
+ download_rfi_attachments,
193
+ download_submittal_attachments,
194
+ get_rfi,
195
+ get_submittal,
196
+ list_companies,
197
+ list_projects,
198
+ list_rfis,
199
+ list_submittals,
200
+ )
201
+
202
+ companies = list_companies()
203
+ projects = list_projects(company_id=123456)
204
+
205
+ rfis = list_rfis(project_id=352338)
206
+ rfi = get_rfi(project_id=352338, rfi_id=102784)
207
+ first_attachment_url = rfi.questions[0].attachments[0].url
208
+
209
+ submittals = list_submittals(project_id=352338)
210
+ submittal = get_submittal(project_id=352338, submittal_id=309641)
211
+ ```
212
+
213
+ Every typed model serializes back to JSON:
214
+
215
+ ```python
216
+ json_payload = rfi.model_dump(mode="json")
217
+ json_string = rfi.model_dump_json()
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Downloading Attachments
223
+
224
+ Attachment URLs live at:
225
+
226
+ ```text
227
+ RFI: questions[].attachments[].url
228
+ Submittal: attachments[].url
229
+ ```
230
+
231
+ Download through the service functions:
232
+
233
+ ```python
234
+ rfi_files = download_rfi_attachments(project_id=352338, rfi_id=102784)
235
+ submittal_files = download_submittal_attachments(
236
+ project_id=352338,
237
+ submittal_id=309641,
238
+ )
239
+ ```
240
+
241
+ The shared file service supports safe filenames, streaming writes, retries, progress logging, batch downloads, and skip-existing behavior by default:
242
+
243
+ ```python
244
+ from pyprocore.services.files import FileDownloadService
245
+
246
+ files = FileDownloadService().download_attachments(
247
+ attachments,
248
+ "downloads/custom",
249
+ fallback_prefix="attachment",
250
+ overwrite=False,
251
+ )
252
+ ```
253
+
254
+ ---
255
+
256
+ ## CLI
257
+
258
+ ```bash
259
+ procore-sdk companies
260
+ procore-sdk projects
261
+ procore-sdk rfis --project 352338
262
+ procore-sdk rfi --project 352338 --id 102784
263
+ procore-sdk submittals --project 352338
264
+ procore-sdk submittal --project 352338 --id 309641
265
+ procore-sdk download-rfi --project 352338 --id 102784
266
+ procore-sdk download-submittal --project 352338 --id 309641
267
+ ```
268
+
269
+ The CLI prints formatted JSON. Typed models are serialized with `model_dump(mode="json")`.
270
+
271
+ ---
272
+
273
+ ## Pagination
274
+
275
+ Collection methods use `ProcoreClient.get_all()`, which follows Procore pagination headers automatically. Business logic should call the service method or `get_all()` directly and never request page 2 by hand.
276
+
277
+ ---
278
+
279
+ ## Logging
280
+
281
+ Structured logs are written to:
282
+
283
+ ```text
284
+ logs/sdk.log
285
+ logs/errors.log
286
+ ```
287
+
288
+ Request logs record method, endpoint, status, elapsed time, and retry count. Exception logs record stack traces, exception type, request URL, HTTP status, and response body when available. The logger redacts sensitive keys such as authorization headers, access tokens, refresh tokens, and client secrets.
289
+
290
+ ---
291
+
292
+ ## Implemented Endpoints (v1.0)
293
+
294
+ ```text
295
+ GET /rest/v1.0/companies
296
+ GET /rest/v1.0/companies/{company_id}/projects
297
+ GET /rest/v1.1/projects/{project_id}/rfis
298
+ GET /rest/v1.1/projects/{project_id}/rfis/{rfi_id}
299
+ GET /rest/v1.1/projects/{project_id}/submittals
300
+ GET /rest/v1.1/projects/{project_id}/submittals/{submittal_id}
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Roadmap
306
+
307
+ **Planned endpoints**
308
+
309
+ - Drawings
310
+ - Documents
311
+ - Specifications
312
+ - Daily Logs
313
+ - Photos
314
+ - Correspondence
315
+ - Observations
316
+
317
+ **Planned capabilities**
318
+
319
+ - AI workflow examples built on the SDK
320
+
321
+ ---
322
+
323
+ ## Troubleshooting
324
+
325
+ | Error | Likely cause and fix |
326
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |
327
+ | `ConfigurationError` | `.env` is missing or a required key is absent. |
328
+ | `AuthenticationError` | Complete the first OAuth code exchange; confirm `pyprocore/auth/token_store.json` holds a refresh token. |
329
+ | `AuthorizationError` | The Procore user lacks access to the target company, project, or resource. |
330
+ | `ResourceNotFoundError` | Project, RFI, or submittal ID is wrong for the configured company. |
331
+ | Attachments not downloading | Check `logs/errors.log` for HTTP status and response body. Existing files are skipped unless `overwrite=True`. |
332
+
333
+ ---
334
+
335
+ ## Tests
336
+
337
+ Run unit tests:
338
+
339
+ ```bash
340
+ .venv/bin/python -m unittest discover -s tests
341
+ ```
342
+
343
+ Run coverage:
344
+
345
+ ```bash
346
+ .venv/bin/python -m coverage run -m unittest discover -s tests
347
+ .venv/bin/python -m coverage report
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Supported Environments
353
+
354
+ - Procore Production
355
+ - Procore Sandbox
356
+
357
+ ---
358
+
359
+ ## Contributing
360
+
361
+ Contributions, issues, and feature requests are welcome. Please open an issue before submitting large changes.
362
+
363
+ ---
364
+
365
+ ## License
366
+
367
+ Released under the MIT License. See [LICENSE](LICENSE) for details.
368
+
369
+ ---
370
+
371
+ > **Disclaimer** — PyProcore is an independent open-source project and is not affiliated with or endorsed by Procore Technologies.
@@ -0,0 +1,335 @@
1
+ # PyProcore
2
+
3
+ > A production-ready Python SDK for building automation, integrations, and AI workflows on top of the Procore REST API.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/pyprocore.svg)](https://pypi.org/project/pyprocore/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/pyprocore.svg)](https://pypi.org/project/pyprocore/)
7
+ [![License](https://img.shields.io/pypi/l/pyprocore.svg)](LICENSE)
8
+ [![Tests](https://github.com/vibhanshu-mishra/pyprocore/actions/workflows/tests.yml/badge.svg)](https://github.com/vibhanshu-mishra/pyprocore/actions/workflows/tests.yml)
9
+ [![Coverage](https://img.shields.io/badge/coverage-94%25-brightgreen.svg)](#tests)
10
+
11
+ PyProcore handles the parts of a Procore integration that are tedious and easy to get wrong — OAuth, token refresh, pagination, retries, typed responses, structured logging, and attachment downloads — so you work with Python objects instead of raw JSON and API plumbing.
12
+
13
+ ---
14
+
15
+ ## Why PyProcore
16
+
17
+ Calling the Procore REST API directly means managing the OAuth handshake, refreshing expired tokens, following pagination headers, retrying failed requests, and parsing untyped JSON on every call.
18
+
19
+ PyProcore does that once, correctly, behind a clean interface. You call a service method and get back a typed Pydantic object. It is designed as the foundation layer for higher-level tools built on Procore data: engineering assistants, document analysis, workflow automation, and AI-powered review.
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ **Authentication and transport**
26
+
27
+ - OAuth 2.0 authorization-code flow
28
+ - Automatic access-token refresh
29
+ - Automatic pagination via Procore response headers
30
+ - Request retries and structured logging with secret redaction
31
+
32
+ **API coverage (v1.0)**
33
+
34
+ - Companies
35
+ - Projects
36
+ - RFIs
37
+ - Submittals
38
+ - Attachment downloads
39
+
40
+ **Developer experience**
41
+
42
+ - Typed Pydantic response models
43
+ - Command-line interface
44
+ - 73 unit tests at 94% coverage, mocked with no live Procore dependency
45
+
46
+ ---
47
+
48
+ ## Architecture
49
+
50
+ | Package | Responsibility |
51
+ | --------------------- | --------------------------------------------------------------- |
52
+ | `pyprocore/auth/` | OAuth exchange, token persistence, token refresh |
53
+ | `pyprocore/core/` | Configuration, endpoint paths, HTTP client, logging, exceptions |
54
+ | `pyprocore/models/` | Pydantic response models |
55
+ | `pyprocore/services/` | Company, project, RFI, submittal, and file services |
56
+ | `pyprocore/parser/` | Email parsing utilities for future automation |
57
+ | `tests/` | Mocked unit tests with no live Procore dependency |
58
+
59
+ ---
60
+
61
+ ## Installation
62
+
63
+ Requires Python 3.12+.
64
+
65
+ ```bash
66
+ pip3 install pyprocore
67
+ ```
68
+
69
+ For local development:
70
+
71
+ ```bash
72
+ git clone https://github.com/vibhanshu-mishra/pyprocore.git
73
+ cd pyprocore
74
+ python3 -m venv .venv
75
+ .venv/bin/python -m pip install --upgrade pip
76
+ .venv/bin/python -m pip install -e .
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Quick Example
82
+
83
+ ```python
84
+ from pyprocore.services import list_projects
85
+
86
+ projects = list_projects(company_id=123456)
87
+
88
+ for project in projects:
89
+ print(project.name)
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Configuration
95
+
96
+ Copy the example file and fill in real values:
97
+
98
+ ```bash
99
+ cp .env.example .env
100
+ ```
101
+
102
+ Required variables:
103
+
104
+ ```bash
105
+ PROCORE_CLIENT_ID=your_client_id
106
+ PROCORE_CLIENT_SECRET=your_client_secret
107
+ PROCORE_REDIRECT_URI=http://localhost:8080/callback
108
+ PROCORE_LOGIN_URL=https://login.procore.com
109
+ PROCORE_API_BASE=https://api.procore.com
110
+ PROCORE_COMPANY_ID=123456
111
+ ```
112
+
113
+ Secrets, tokens, URLs, and company IDs are never hardcoded in source.
114
+
115
+ ---
116
+
117
+ ## Authentication
118
+
119
+ Exchange the first authorization code and save the token locally:
120
+
121
+ ```python
122
+ from pyprocore.auth.oauth import exchange_authorization_code
123
+ from pyprocore.auth.token_manager import TokenManager
124
+
125
+ token_response = exchange_authorization_code("authorization-code-from-procore")
126
+ TokenManager().save_oauth_response(token_response)
127
+ ```
128
+
129
+ After that, SDK clients read the token automatically:
130
+
131
+ ```python
132
+ from pyprocore.auth.token_manager import get_access_token
133
+
134
+ access_token = get_access_token()
135
+ ```
136
+
137
+ Expired access tokens refresh automatically whenever a refresh token is available.
138
+
139
+ ---
140
+
141
+ ## Usage
142
+
143
+ Once `.env` is configured and the one-time OAuth exchange is complete, calls return typed objects:
144
+
145
+ ```python
146
+ from pyprocore.services import list_projects
147
+
148
+ for project in list_projects(company_id=123456):
149
+ print(project.name)
150
+ ```
151
+
152
+ Full service surface:
153
+
154
+ ```python
155
+ from pyprocore.services import (
156
+ download_rfi_attachments,
157
+ download_submittal_attachments,
158
+ get_rfi,
159
+ get_submittal,
160
+ list_companies,
161
+ list_projects,
162
+ list_rfis,
163
+ list_submittals,
164
+ )
165
+
166
+ companies = list_companies()
167
+ projects = list_projects(company_id=123456)
168
+
169
+ rfis = list_rfis(project_id=352338)
170
+ rfi = get_rfi(project_id=352338, rfi_id=102784)
171
+ first_attachment_url = rfi.questions[0].attachments[0].url
172
+
173
+ submittals = list_submittals(project_id=352338)
174
+ submittal = get_submittal(project_id=352338, submittal_id=309641)
175
+ ```
176
+
177
+ Every typed model serializes back to JSON:
178
+
179
+ ```python
180
+ json_payload = rfi.model_dump(mode="json")
181
+ json_string = rfi.model_dump_json()
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Downloading Attachments
187
+
188
+ Attachment URLs live at:
189
+
190
+ ```text
191
+ RFI: questions[].attachments[].url
192
+ Submittal: attachments[].url
193
+ ```
194
+
195
+ Download through the service functions:
196
+
197
+ ```python
198
+ rfi_files = download_rfi_attachments(project_id=352338, rfi_id=102784)
199
+ submittal_files = download_submittal_attachments(
200
+ project_id=352338,
201
+ submittal_id=309641,
202
+ )
203
+ ```
204
+
205
+ The shared file service supports safe filenames, streaming writes, retries, progress logging, batch downloads, and skip-existing behavior by default:
206
+
207
+ ```python
208
+ from pyprocore.services.files import FileDownloadService
209
+
210
+ files = FileDownloadService().download_attachments(
211
+ attachments,
212
+ "downloads/custom",
213
+ fallback_prefix="attachment",
214
+ overwrite=False,
215
+ )
216
+ ```
217
+
218
+ ---
219
+
220
+ ## CLI
221
+
222
+ ```bash
223
+ procore-sdk companies
224
+ procore-sdk projects
225
+ procore-sdk rfis --project 352338
226
+ procore-sdk rfi --project 352338 --id 102784
227
+ procore-sdk submittals --project 352338
228
+ procore-sdk submittal --project 352338 --id 309641
229
+ procore-sdk download-rfi --project 352338 --id 102784
230
+ procore-sdk download-submittal --project 352338 --id 309641
231
+ ```
232
+
233
+ The CLI prints formatted JSON. Typed models are serialized with `model_dump(mode="json")`.
234
+
235
+ ---
236
+
237
+ ## Pagination
238
+
239
+ Collection methods use `ProcoreClient.get_all()`, which follows Procore pagination headers automatically. Business logic should call the service method or `get_all()` directly and never request page 2 by hand.
240
+
241
+ ---
242
+
243
+ ## Logging
244
+
245
+ Structured logs are written to:
246
+
247
+ ```text
248
+ logs/sdk.log
249
+ logs/errors.log
250
+ ```
251
+
252
+ Request logs record method, endpoint, status, elapsed time, and retry count. Exception logs record stack traces, exception type, request URL, HTTP status, and response body when available. The logger redacts sensitive keys such as authorization headers, access tokens, refresh tokens, and client secrets.
253
+
254
+ ---
255
+
256
+ ## Implemented Endpoints (v1.0)
257
+
258
+ ```text
259
+ GET /rest/v1.0/companies
260
+ GET /rest/v1.0/companies/{company_id}/projects
261
+ GET /rest/v1.1/projects/{project_id}/rfis
262
+ GET /rest/v1.1/projects/{project_id}/rfis/{rfi_id}
263
+ GET /rest/v1.1/projects/{project_id}/submittals
264
+ GET /rest/v1.1/projects/{project_id}/submittals/{submittal_id}
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Roadmap
270
+
271
+ **Planned endpoints**
272
+
273
+ - Drawings
274
+ - Documents
275
+ - Specifications
276
+ - Daily Logs
277
+ - Photos
278
+ - Correspondence
279
+ - Observations
280
+
281
+ **Planned capabilities**
282
+
283
+ - AI workflow examples built on the SDK
284
+
285
+ ---
286
+
287
+ ## Troubleshooting
288
+
289
+ | Error | Likely cause and fix |
290
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |
291
+ | `ConfigurationError` | `.env` is missing or a required key is absent. |
292
+ | `AuthenticationError` | Complete the first OAuth code exchange; confirm `pyprocore/auth/token_store.json` holds a refresh token. |
293
+ | `AuthorizationError` | The Procore user lacks access to the target company, project, or resource. |
294
+ | `ResourceNotFoundError` | Project, RFI, or submittal ID is wrong for the configured company. |
295
+ | Attachments not downloading | Check `logs/errors.log` for HTTP status and response body. Existing files are skipped unless `overwrite=True`. |
296
+
297
+ ---
298
+
299
+ ## Tests
300
+
301
+ Run unit tests:
302
+
303
+ ```bash
304
+ .venv/bin/python -m unittest discover -s tests
305
+ ```
306
+
307
+ Run coverage:
308
+
309
+ ```bash
310
+ .venv/bin/python -m coverage run -m unittest discover -s tests
311
+ .venv/bin/python -m coverage report
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Supported Environments
317
+
318
+ - Procore Production
319
+ - Procore Sandbox
320
+
321
+ ---
322
+
323
+ ## Contributing
324
+
325
+ Contributions, issues, and feature requests are welcome. Please open an issue before submitting large changes.
326
+
327
+ ---
328
+
329
+ ## License
330
+
331
+ Released under the MIT License. See [LICENSE](LICENSE) for details.
332
+
333
+ ---
334
+
335
+ > **Disclaimer** — PyProcore is an independent open-source project and is not affiliated with or endorsed by Procore Technologies.
@@ -0,0 +1,3 @@
1
+ """Python SDK for the Procore REST API."""
2
+
3
+ __version__ = "1.0.1"