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.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include tests *.py
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