adpapi 0.1.0__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.
- adpapi-0.1.0/MANIFEST.in +3 -0
- adpapi-0.1.0/PKG-INFO +701 -0
- adpapi-0.1.0/README.md +676 -0
- adpapi-0.1.0/adpapi.egg-info/PKG-INFO +701 -0
- adpapi-0.1.0/adpapi.egg-info/SOURCES.txt +19 -0
- adpapi-0.1.0/adpapi.egg-info/dependency_links.txt +1 -0
- adpapi-0.1.0/adpapi.egg-info/not-zip-safe +1 -0
- adpapi-0.1.0/adpapi.egg-info/requires.txt +8 -0
- adpapi-0.1.0/adpapi.egg-info/top_level.txt +5 -0
- adpapi-0.1.0/client.py +210 -0
- adpapi-0.1.0/logger.py +17 -0
- adpapi-0.1.0/main.py +46 -0
- adpapi-0.1.0/pyproject.toml +26 -0
- adpapi-0.1.0/sessions.py +103 -0
- adpapi-0.1.0/setup.cfg +4 -0
- adpapi-0.1.0/setup.py +26 -0
- adpapi-0.1.0/tests/__init__.py +1 -0
- adpapi-0.1.0/tests/conftest.py +39 -0
- adpapi-0.1.0/tests/test_client.py +375 -0
- adpapi-0.1.0/tests/test_logger.py +83 -0
- adpapi-0.1.0/tests/test_sessions.py +277 -0
adpapi-0.1.0/MANIFEST.in
ADDED
adpapi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adpapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Home-page: https://github.com/JoeyRussoniello/Adp-Api-Client
|
|
6
|
+
Author: Joey Russoniello
|
|
7
|
+
Author-email: Joey Russoniello <jmrusso@bu.edu>
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
16
|
+
Requires-Dist: requests>=2.32.5
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
|
|
26
|
+
# ADP API Client
|
|
27
|
+
|
|
28
|
+
A robust Python client for the ADP Workforce Now API with automatic token management, retry logic, and comprehensive error handling.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
**Token Management**
|
|
33
|
+
- Automatic token acquisition with OAuth2 client credentials flow
|
|
34
|
+
- Automatic token refresh before expiration
|
|
35
|
+
- 5-minute buffer to prevent mid-request token expiration
|
|
36
|
+
|
|
37
|
+
**Resilience & Reliability**
|
|
38
|
+
- Exponential backoff retry strategy for transient failures
|
|
39
|
+
- Handles HTTP 429 (rate limiting), 500, 502, 503, 504 errors
|
|
40
|
+
- Configurable timeouts and page sizes
|
|
41
|
+
- Comprehensive error handling
|
|
42
|
+
|
|
43
|
+
**Security**
|
|
44
|
+
- Certificate-based authentication (mTLS)
|
|
45
|
+
- Masking support for sensitive PII data
|
|
46
|
+
- Proper session management and cleanup
|
|
47
|
+
|
|
48
|
+
**Data Operations**
|
|
49
|
+
- Flexible column selection via OData `$select`
|
|
50
|
+
- Automatic pagination with configurable page sizes
|
|
51
|
+
- Support for unmasked data retrieval when authorized
|
|
52
|
+
|
|
53
|
+
**Testing & Development**
|
|
54
|
+
- 42 comprehensive unit tests
|
|
55
|
+
- Full mock support for testing
|
|
56
|
+
- Context manager support for resource cleanup
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
### Prerequisites
|
|
61
|
+
- Python 3.10 or higher
|
|
62
|
+
- ADP Workforce Now API credentials
|
|
63
|
+
- Client certificate and key files
|
|
64
|
+
|
|
65
|
+
### Setup
|
|
66
|
+
|
|
67
|
+
1. **Clone the repository**
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/trinity-property-consultants/API-Clients
|
|
70
|
+
cd ADP
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
2. **Create a virtual environment**
|
|
74
|
+
```bash
|
|
75
|
+
python -m venv .venv
|
|
76
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. **Install dependencies**
|
|
80
|
+
```bash
|
|
81
|
+
pip install requests python-dotenv
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
4. **Configure credentials**
|
|
85
|
+
Create a `.env` file in the project root:
|
|
86
|
+
|
|
87
|
+
```env
|
|
88
|
+
CLIENT_ID=your_client_id
|
|
89
|
+
CLIENT_SECRET=your_client_secret
|
|
90
|
+
CERT_PATH=path/to/certificate.pem
|
|
91
|
+
KEY_PATH=path/to/adp.key
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from client import AdpApiClient
|
|
98
|
+
|
|
99
|
+
# Initialize the client
|
|
100
|
+
client = AdpApiClient(
|
|
101
|
+
client_id="your_client_id",
|
|
102
|
+
client_secret="your_client_secret",
|
|
103
|
+
cert_path="certificate.pem",
|
|
104
|
+
key_path="adp.key"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Fetch worker data with specific columns
|
|
108
|
+
workers = client.call_endpoint(
|
|
109
|
+
endpoint="/hr/v2/workers",
|
|
110
|
+
cols=[
|
|
111
|
+
"workers/person/legalName",
|
|
112
|
+
"workers/person/birthDate",
|
|
113
|
+
"workers/workAssignments/reportsTo",
|
|
114
|
+
"workers/businessCommunication/emails"
|
|
115
|
+
],
|
|
116
|
+
masked=False # Request unmasked PII (if authorized)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Process the results
|
|
120
|
+
for response in workers:
|
|
121
|
+
print(response)
|
|
122
|
+
|
|
123
|
+
# Use context manager for automatic cleanup
|
|
124
|
+
with AdpApiClient(...) as client:
|
|
125
|
+
workers = client.call_endpoint(...)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
### Environment Variables
|
|
131
|
+
|
|
132
|
+
```env
|
|
133
|
+
CLIENT_ID # OAuth2 client ID
|
|
134
|
+
CLIENT_SECRET # OAuth2 client secret
|
|
135
|
+
CERT_PATH # Path to mTLS certificate (default: certificate.pem)
|
|
136
|
+
KEY_PATH # Path to mTLS key (default: adp.key)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Client Parameters
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
AdpApiClient(
|
|
143
|
+
client_id: str, # Required: OAuth2 client ID
|
|
144
|
+
client_secret: str, # Required: OAuth2 client secret
|
|
145
|
+
cert_path: str, # Required: Path to certificate file
|
|
146
|
+
key_path: str # Required: Path to key file
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Endpoint Call Parameters
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
client.call_endpoint(
|
|
154
|
+
endpoint: str, # Required: API endpoint (e.g., "/hr/v2/workers")
|
|
155
|
+
cols: List[str], # Required: Columns to retrieve (OData $select)
|
|
156
|
+
masked: bool = True, # Optional: Mask sensitive data (default: True)
|
|
157
|
+
timeout: int = 30, # Optional: Request timeout in seconds (default: 30)
|
|
158
|
+
page_size: int = 100, # Optional: Records per page, max 100 (default: 100)
|
|
159
|
+
max_requests: int = None # Optional: Max API calls (for testing)
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## API Reference
|
|
164
|
+
|
|
165
|
+
### AdpApiClient
|
|
166
|
+
|
|
167
|
+
Main client class for interacting with the ADP API.
|
|
168
|
+
|
|
169
|
+
#### Methods
|
|
170
|
+
|
|
171
|
+
**`__init__(client_id, client_secret, cert_path, key_path)`**
|
|
172
|
+
- Initializes the client with credentials
|
|
173
|
+
- Validates certificate and key files exist
|
|
174
|
+
- Acquires initial OAuth2 token
|
|
175
|
+
- Raises: `ValueError`, `FileNotFoundError`
|
|
176
|
+
|
|
177
|
+
**`call_endpoint(endpoint, cols, masked=True, timeout=30, page_size=100, max_requests=None)`**
|
|
178
|
+
- Calls an ADP API endpoint with pagination support
|
|
179
|
+
- Returns: `List[Dict]` - List of API response objects
|
|
180
|
+
- Raises: `ValueError` for invalid endpoint format
|
|
181
|
+
|
|
182
|
+
**`_get_headers(masked=True)`**
|
|
183
|
+
- Generates request headers with Bearer token and masking preference
|
|
184
|
+
- Returns: `Dict[str, str]` - HTTP headers
|
|
185
|
+
- Automatically refreshes expired tokens
|
|
186
|
+
|
|
187
|
+
**`_ensure_valid_token(timeout=30)`**
|
|
188
|
+
- Checks token expiration and refreshes if needed
|
|
189
|
+
- Automatically called before each request
|
|
190
|
+
|
|
191
|
+
**`__enter__()` / `__exit__()`**
|
|
192
|
+
- Context manager support for automatic session cleanup
|
|
193
|
+
- Usage: `with AdpApiClient(...) as client:`
|
|
194
|
+
|
|
195
|
+
### ApiSession
|
|
196
|
+
|
|
197
|
+
Low-level HTTP session wrapper for ADP API requests.
|
|
198
|
+
|
|
199
|
+
#### Methods
|
|
200
|
+
|
|
201
|
+
**`get(url: str)`** - GET request
|
|
202
|
+
**`post(url: str, data: Optional[Any] = None)`** - POST request with optional JSON body
|
|
203
|
+
**`put(url: str, data: Optional[Any] = None)`** - PUT request with optional JSON body
|
|
204
|
+
**`delete(url: str)`** - DELETE request
|
|
205
|
+
|
|
206
|
+
#### Parameters
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
ApiSession(
|
|
210
|
+
session: requests.Session,
|
|
211
|
+
cert: tuple[str, str], # (cert_path, key_path)
|
|
212
|
+
headers: Optional[dict] = None,
|
|
213
|
+
params: Optional[dict] = None,
|
|
214
|
+
timeout: int = 30,
|
|
215
|
+
data: Optional[Any] = None
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Usage Examples
|
|
220
|
+
|
|
221
|
+
### Basic Worker Retrieval
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from client import AdpApiClient
|
|
225
|
+
import os
|
|
226
|
+
from dotenv import load_dotenv
|
|
227
|
+
|
|
228
|
+
# Load environment variables
|
|
229
|
+
load_dotenv()
|
|
230
|
+
|
|
231
|
+
# Initialize client
|
|
232
|
+
api = AdpApiClient(
|
|
233
|
+
client_id=os.getenv("CLIENT_ID"),
|
|
234
|
+
client_secret=os.getenv("CLIENT_SECRET"),
|
|
235
|
+
cert_path=os.getenv("CERT_PATH"),
|
|
236
|
+
key_path=os.getenv("KEY_PATH")
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Get all workers
|
|
240
|
+
workers = api.call_endpoint(
|
|
241
|
+
endpoint="/hr/v2/workers",
|
|
242
|
+
cols=[
|
|
243
|
+
"workers/person/legalName",
|
|
244
|
+
"workers/person/birthDate",
|
|
245
|
+
"workers/workAssignments/reportsTo"
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
print(f"Retrieved {len(workers)} worker records")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Masked vs Unmasked Data
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# With masking (default) - PII is masked
|
|
256
|
+
workers_masked = api.call_endpoint(
|
|
257
|
+
endpoint="/hr/v2/workers",
|
|
258
|
+
cols=["workers/person/birthDate"],
|
|
259
|
+
masked=True
|
|
260
|
+
)
|
|
261
|
+
# Output: {"workers": [{"person": {"birthDate": "0000-00-00"}}]}
|
|
262
|
+
|
|
263
|
+
# Without masking - Full PII (if authorized)
|
|
264
|
+
workers_unmasked = api.call_endpoint(
|
|
265
|
+
endpoint="/hr/v2/workers",
|
|
266
|
+
cols=["workers/person/birthDate"],
|
|
267
|
+
masked=False
|
|
268
|
+
)
|
|
269
|
+
# Output: {"workers": [{"person": {"birthDate": "1990-05-15"}}]}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Pagination Control
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
# Request with custom page size and limit
|
|
276
|
+
workers = api.call_endpoint(
|
|
277
|
+
endpoint="/hr/v2/workers",
|
|
278
|
+
cols=["workers/person/legalName"],
|
|
279
|
+
page_size=50, # Fetch 50 records per request
|
|
280
|
+
max_requests=10 # Stop after 10 API calls (500 records max)
|
|
281
|
+
)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Context Manager (Recommended)
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# Automatic session cleanup
|
|
288
|
+
with AdpApiClient(
|
|
289
|
+
client_id=os.getenv("CLIENT_ID"),
|
|
290
|
+
client_secret=os.getenv("CLIENT_SECRET"),
|
|
291
|
+
cert_path=os.getenv("CERT_PATH"),
|
|
292
|
+
key_path=os.getenv("KEY_PATH")
|
|
293
|
+
) as api:
|
|
294
|
+
workers = api.call_endpoint(
|
|
295
|
+
endpoint="/hr/v2/workers",
|
|
296
|
+
cols=["workers/person/legalName"]
|
|
297
|
+
)
|
|
298
|
+
# Session automatically closed here
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Error Handling
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
import requests
|
|
305
|
+
from client import AdpApiClient
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
api = AdpApiClient(
|
|
309
|
+
client_id="invalid_id",
|
|
310
|
+
client_secret="invalid_secret",
|
|
311
|
+
cert_path="cert.pem",
|
|
312
|
+
key_path="key.key"
|
|
313
|
+
)
|
|
314
|
+
except FileNotFoundError as e:
|
|
315
|
+
print(f"Certificate not found: {e}")
|
|
316
|
+
except ValueError as e:
|
|
317
|
+
print(f"Invalid credentials: {e}")
|
|
318
|
+
except requests.RequestException as e:
|
|
319
|
+
print(f"Token request failed: {e}")
|
|
320
|
+
|
|
321
|
+
# Endpoint call errors
|
|
322
|
+
try:
|
|
323
|
+
workers = api.call_endpoint(
|
|
324
|
+
endpoint="invalid_endpoint",
|
|
325
|
+
cols=["workers/person/legalName"]
|
|
326
|
+
)
|
|
327
|
+
except ValueError as e:
|
|
328
|
+
print(f"Invalid endpoint format: {e}")
|
|
329
|
+
except requests.RequestException as e:
|
|
330
|
+
print(f"API request failed: {e}")
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Architecture
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
ADP API Client
|
|
337
|
+
├── client.py # Main AdpApiClient class
|
|
338
|
+
│ ├── Token management
|
|
339
|
+
│ ├── Retry strategy
|
|
340
|
+
│ ├── Request headers
|
|
341
|
+
│ └── Endpoint calling
|
|
342
|
+
├── sessions.py # ApiSession wrapper
|
|
343
|
+
│ ├── HTTP method routing
|
|
344
|
+
│ ├── Parameter handling
|
|
345
|
+
│ └── Error handling
|
|
346
|
+
├── logger.py # Logging configuration
|
|
347
|
+
└── main.py # Example usage
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Key Components
|
|
351
|
+
|
|
352
|
+
**AdpApiClient**
|
|
353
|
+
- OAuth2 token acquisition and refresh
|
|
354
|
+
- Automatic token expiration detection
|
|
355
|
+
- Request retry strategy
|
|
356
|
+
- Pagination support
|
|
357
|
+
- Header generation with masking control
|
|
358
|
+
|
|
359
|
+
**ApiSession**
|
|
360
|
+
- Low-level HTTP operations
|
|
361
|
+
- Certificate-based authentication
|
|
362
|
+
- Exception handling and logging
|
|
363
|
+
- Request parameter management
|
|
364
|
+
|
|
365
|
+
**Token Management**
|
|
366
|
+
- Tokens acquired via OAuth2 client credentials flow
|
|
367
|
+
- Automatic refresh with 5-minute expiration buffer
|
|
368
|
+
- Prevents mid-request token expiration
|
|
369
|
+
|
|
370
|
+
**Retry Strategy**
|
|
371
|
+
- Exponential backoff (3 retries, 0.5s factor)
|
|
372
|
+
- Retries on transient errors: 429, 500, 502, 503, 504
|
|
373
|
+
- Configurable via `_setup_retry_strategy()`
|
|
374
|
+
|
|
375
|
+
## Testing
|
|
376
|
+
|
|
377
|
+
### Run All Tests
|
|
378
|
+
```bash
|
|
379
|
+
python -m unittest discover tests/ -v
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Test Coverage
|
|
383
|
+
- **42 comprehensive unit tests** across all components
|
|
384
|
+
- Tests for initialization, token management, retries, error handling
|
|
385
|
+
- Mock-based testing with isolated dependencies
|
|
386
|
+
- ~0.26 second execution time
|
|
387
|
+
|
|
388
|
+
### Test Structure
|
|
389
|
+
```
|
|
390
|
+
tests/
|
|
391
|
+
├── test_client.py # 26 tests for AdpApiClient
|
|
392
|
+
├── test_sessions.py # 15 tests for ApiSession
|
|
393
|
+
├── test_logger.py # 4 tests for logging
|
|
394
|
+
└── README.md # Testing documentation
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Run Specific Tests
|
|
398
|
+
```bash
|
|
399
|
+
# Run specific test file
|
|
400
|
+
python -m unittest tests.test_client -v
|
|
401
|
+
|
|
402
|
+
# Run specific test class
|
|
403
|
+
python -m unittest tests.test_client.TestAdpApiClientInitialization -v
|
|
404
|
+
|
|
405
|
+
# Run specific test
|
|
406
|
+
python -m unittest tests.test_client.TestAdpApiClientInitialization.test_initialization_success -v
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Logging
|
|
410
|
+
|
|
411
|
+
Logging is configured in `logger.py` and outputs to both file and console.
|
|
412
|
+
|
|
413
|
+
### Configuration
|
|
414
|
+
|
|
415
|
+
```python
|
|
416
|
+
from logger import configure_logging
|
|
417
|
+
|
|
418
|
+
# Initialize logging
|
|
419
|
+
configure_logging()
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Log Output
|
|
423
|
+
- **File**: `app.log` (DEBUG level)
|
|
424
|
+
- **Console**: stdout (DEBUG level)
|
|
425
|
+
- **Format**: `%(asctime)s - %(levelname)s - %(message)s`
|
|
426
|
+
|
|
427
|
+
### Log Events
|
|
428
|
+
```
|
|
429
|
+
Token Acquired (expires in 3600s)
|
|
430
|
+
Retry strategy configured: 3 retries with 0.5s backoff
|
|
431
|
+
Request to /hr/v2/workers
|
|
432
|
+
End of pagination reached (204 No Content)
|
|
433
|
+
Session closed
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Error Handling
|
|
437
|
+
|
|
438
|
+
### Common Errors
|
|
439
|
+
|
|
440
|
+
| Error | Cause | Solution |
|
|
441
|
+
|-------|-------|----------|
|
|
442
|
+
| `FileNotFoundError` | Certificate/key file missing | Verify certificate and key paths |
|
|
443
|
+
| `ValueError: All credentials and paths must be provided` | Missing credentials | Check CLIENT_ID, CLIENT_SECRET, CERT_PATH, KEY_PATH |
|
|
444
|
+
| `ValueError: No access token in response` | Invalid credentials | Verify CLIENT_ID and CLIENT_SECRET |
|
|
445
|
+
| `ValueError: Incorrect Endpoint Received` | Invalid endpoint format | Use format: `/hr/v2/workers` or full URL |
|
|
446
|
+
| `requests.RequestException` | Network/API error | Check internet connection, API status |
|
|
447
|
+
| `requests.exceptions.HTTPError` | HTTP error (4xx, 5xx) | Check authorization, endpoint validity |
|
|
448
|
+
|
|
449
|
+
### Retry Behavior
|
|
450
|
+
|
|
451
|
+
The client automatically retries on transient errors:
|
|
452
|
+
- HTTP 429 (Rate Limiting)
|
|
453
|
+
- HTTP 500 (Internal Server Error)
|
|
454
|
+
- HTTP 502 (Bad Gateway)
|
|
455
|
+
- HTTP 503 (Service Unavailable)
|
|
456
|
+
- HTTP 504 (Gateway Timeout)
|
|
457
|
+
|
|
458
|
+
Retry strategy:
|
|
459
|
+
- Maximum 3 retries
|
|
460
|
+
- Exponential backoff: 0.5s, 1.0s, 1.5s
|
|
461
|
+
- Not retried: client errors (4xx), successful responses
|
|
462
|
+
|
|
463
|
+
## Best Practices
|
|
464
|
+
|
|
465
|
+
### 1. Use Context Managers
|
|
466
|
+
```python
|
|
467
|
+
# ✅ Good - Automatic cleanup
|
|
468
|
+
with AdpApiClient(...) as client:
|
|
469
|
+
workers = client.call_endpoint(...)
|
|
470
|
+
|
|
471
|
+
# ❌ Avoid - Manual cleanup required
|
|
472
|
+
client = AdpApiClient(...)
|
|
473
|
+
workers = client.call_endpoint(...)
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 2. Request Unmasked Data Only When Needed
|
|
477
|
+
```python
|
|
478
|
+
# ✅ Good - Masked by default for security
|
|
479
|
+
workers = client.call_endpoint(endpoint, cols)
|
|
480
|
+
|
|
481
|
+
# ⚠️ Caution - Only use with proper authorization
|
|
482
|
+
workers = client.call_endpoint(endpoint, cols, masked=False)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### 3. Handle Errors Gracefully
|
|
486
|
+
```python
|
|
487
|
+
# ✅ Good - Comprehensive error handling
|
|
488
|
+
try:
|
|
489
|
+
workers = client.call_endpoint(...)
|
|
490
|
+
except requests.RequestException as e:
|
|
491
|
+
logger.error(f"API error: {e}")
|
|
492
|
+
except ValueError as e:
|
|
493
|
+
logger.error(f"Invalid input: {e}")
|
|
494
|
+
|
|
495
|
+
# ❌ Avoid - Silent failures
|
|
496
|
+
try:
|
|
497
|
+
workers = client.call_endpoint(...)
|
|
498
|
+
except:
|
|
499
|
+
pass
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 4. Configure Appropriate Timeouts
|
|
503
|
+
```python
|
|
504
|
+
# ✅ Good - Sufficient timeout for large datasets
|
|
505
|
+
workers = client.call_endpoint(
|
|
506
|
+
endpoint,
|
|
507
|
+
cols,
|
|
508
|
+
timeout=60, # 60 seconds for large requests
|
|
509
|
+
page_size=100 # Max page size
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# ❌ Avoid - Too short timeout
|
|
513
|
+
workers = client.call_endpoint(endpoint, cols, timeout=5)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 5. Store Credentials Securely
|
|
517
|
+
```python
|
|
518
|
+
# ✅ Good - Use environment variables
|
|
519
|
+
load_dotenv()
|
|
520
|
+
client = AdpApiClient(
|
|
521
|
+
client_id=os.getenv("CLIENT_ID"),
|
|
522
|
+
client_secret=os.getenv("CLIENT_SECRET"),
|
|
523
|
+
...
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# ❌ Avoid - Hardcoded credentials
|
|
527
|
+
client = AdpApiClient(
|
|
528
|
+
client_id="actual_id",
|
|
529
|
+
client_secret="actual_secret",
|
|
530
|
+
...
|
|
531
|
+
)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Troubleshooting
|
|
535
|
+
|
|
536
|
+
### Token Acquisition Fails
|
|
537
|
+
|
|
538
|
+
**Error**: `Token request failed: 401 Unauthorized`
|
|
539
|
+
|
|
540
|
+
**Solutions**:
|
|
541
|
+
1. Verify CLIENT_ID and CLIENT_SECRET are correct
|
|
542
|
+
2. Check certificate and key files exist
|
|
543
|
+
3. Ensure certificate/key have proper permissions
|
|
544
|
+
4. Verify ADP API endpoint is accessible
|
|
545
|
+
|
|
546
|
+
### Birth Dates Show "0000-00-00"
|
|
547
|
+
|
|
548
|
+
**Cause**: Insufficient permissions to access unmasked data
|
|
549
|
+
|
|
550
|
+
**Solution**:
|
|
551
|
+
1. Check if your account has PII access permissions
|
|
552
|
+
2. Contact ADP administrator to grant unmasked data access
|
|
553
|
+
3. Verify `masked=False` parameter is working (see code review notes)
|
|
554
|
+
|
|
555
|
+
### Pagination Issues
|
|
556
|
+
|
|
557
|
+
**Error**: `Incorrect Endpoint Received`
|
|
558
|
+
|
|
559
|
+
**Solutions**:
|
|
560
|
+
1. Use proper endpoint format: `/hr/v2/workers` or `https://api.adp.com/hr/v2/workers`
|
|
561
|
+
2. Ensure columns are valid OData format: `workers/person/legalName`
|
|
562
|
+
3. Check page size is between 1-100
|
|
563
|
+
|
|
564
|
+
### Connection Timeouts
|
|
565
|
+
|
|
566
|
+
**Error**: `requests.exceptions.ConnectTimeout`
|
|
567
|
+
|
|
568
|
+
**Solutions**:
|
|
569
|
+
1. Increase timeout: `timeout=60`
|
|
570
|
+
2. Check internet connection
|
|
571
|
+
3. Verify ADP API service is operational
|
|
572
|
+
4. Check firewall/proxy settings
|
|
573
|
+
|
|
574
|
+
## Performance Considerations
|
|
575
|
+
|
|
576
|
+
### Pagination
|
|
577
|
+
- Default page size: 100 records
|
|
578
|
+
- Maximum page size: 100 records
|
|
579
|
+
- Adjust based on network speed and data size
|
|
580
|
+
|
|
581
|
+
### Timeouts
|
|
582
|
+
- Default: 30 seconds per request
|
|
583
|
+
- Increase for large datasets or slow connections
|
|
584
|
+
- Decrease for quick fail-fast behavior
|
|
585
|
+
|
|
586
|
+
### Retry Strategy
|
|
587
|
+
- Default: 3 retries with exponential backoff
|
|
588
|
+
- Customize via `_setup_retry_strategy(retries, backoff_factor)`
|
|
589
|
+
|
|
590
|
+
### Token Management
|
|
591
|
+
- Token refresh buffer: 5 minutes
|
|
592
|
+
- Reduces chance of mid-request expiration
|
|
593
|
+
- Automatic and transparent to caller
|
|
594
|
+
|
|
595
|
+
## Project Structure
|
|
596
|
+
|
|
597
|
+
```
|
|
598
|
+
ADP/
|
|
599
|
+
├── client.py # Main API client
|
|
600
|
+
├── sessions.py # HTTP session wrapper
|
|
601
|
+
├── logger.py # Logging configuration
|
|
602
|
+
├── main.py # Example usage script
|
|
603
|
+
├── proccessing.py # Data processing utilities
|
|
604
|
+
├── worker_data.json # Sample output
|
|
605
|
+
├── .env # Configuration (gitignored)
|
|
606
|
+
├── .gitignore
|
|
607
|
+
├── README.md # This file
|
|
608
|
+
├── TESTING.md # Testing documentation
|
|
609
|
+
├── requirements.txt # Python dependencies
|
|
610
|
+
└── tests/
|
|
611
|
+
├── __init__.py
|
|
612
|
+
├── test_client.py # Client tests
|
|
613
|
+
├── test_sessions.py # Session tests
|
|
614
|
+
├── test_logger.py # Logger tests
|
|
615
|
+
├── conftest.py # Test configuration
|
|
616
|
+
└── README.md # Testing guide
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## Development
|
|
620
|
+
|
|
621
|
+
### Install Development Dependencies
|
|
622
|
+
```bash
|
|
623
|
+
pip install -r requirements.txt
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Run Tests
|
|
627
|
+
```bash
|
|
628
|
+
python -m unittest discover tests/ -v
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Code Quality
|
|
632
|
+
- Type hints throughout
|
|
633
|
+
- Comprehensive docstrings
|
|
634
|
+
- Error handling at all integration points
|
|
635
|
+
- Logging at key operations
|
|
636
|
+
|
|
637
|
+
### Adding New Features
|
|
638
|
+
1. Create feature branch
|
|
639
|
+
2. Add tests first (TDD approach)
|
|
640
|
+
3. Implement feature
|
|
641
|
+
4. Run all tests
|
|
642
|
+
5. Update documentation
|
|
643
|
+
6. Submit pull request
|
|
644
|
+
|
|
645
|
+
## Limitations & Known Issues
|
|
646
|
+
|
|
647
|
+
### Masked Data
|
|
648
|
+
- When `masked=False`, birth dates may still show "0000-00-00" if permissions are insufficient
|
|
649
|
+
- Contact ADP to enable unmasked PII access
|
|
650
|
+
|
|
651
|
+
### API Limitations
|
|
652
|
+
- Maximum page size: 100 records
|
|
653
|
+
- Rate limiting: varies by ADP subscription
|
|
654
|
+
- Retry strategy: only retries transient (5xx) errors
|
|
655
|
+
|
|
656
|
+
### Token Lifecycle
|
|
657
|
+
- Token expiration buffer: fixed at 5 minutes
|
|
658
|
+
- No token invalidation on logout (handled by ADP)
|
|
659
|
+
|
|
660
|
+
## Contributing
|
|
661
|
+
|
|
662
|
+
1. Fork the repository
|
|
663
|
+
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
|
|
664
|
+
3. Make your changes
|
|
665
|
+
4. Add/update tests
|
|
666
|
+
5. Run full test suite
|
|
667
|
+
6. Commit your changes (`git commit -m 'Add AmazingFeature'`)
|
|
668
|
+
7. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
669
|
+
8. Open a Pull Request
|
|
670
|
+
|
|
671
|
+
## License
|
|
672
|
+
|
|
673
|
+
Internal use only. This client is designed for integration with ADP Workforce Now API.
|
|
674
|
+
|
|
675
|
+
## Support
|
|
676
|
+
|
|
677
|
+
For issues or questions:
|
|
678
|
+
1. Check the troubleshooting section
|
|
679
|
+
2. Review test files for usage examples
|
|
680
|
+
3. Consult ADP Workforce Now API documentation
|
|
681
|
+
4. Contact your ADP administrator
|
|
682
|
+
|
|
683
|
+
## Related Documentation
|
|
684
|
+
|
|
685
|
+
- [TESTING.md](TESTING.md) - Comprehensive testing guide
|
|
686
|
+
- [Code Review Notes](./docs/code_review.md) - Architecture and design decisions
|
|
687
|
+
- ADP Workforce Now API Documentation: https://developer.adp.com/
|
|
688
|
+
|
|
689
|
+
## Changelog
|
|
690
|
+
|
|
691
|
+
### Version 1.0.0 (Current)
|
|
692
|
+
- ✅ OAuth2 client credentials authentication
|
|
693
|
+
- ✅ Automatic token management and refresh
|
|
694
|
+
- ✅ Exponential backoff retry strategy
|
|
695
|
+
- ✅ OData $select support for column selection
|
|
696
|
+
- ✅ Automatic pagination
|
|
697
|
+
- ✅ Masking support for sensitive data
|
|
698
|
+
- ✅ Comprehensive error handling
|
|
699
|
+
- ✅ Context manager support
|
|
700
|
+
- ✅ 42 comprehensive unit tests
|
|
701
|
+
- ✅ Full documentation and examples
|