axa-fr-oidc 1.3.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,6 @@
1
+ .idea/
2
+ __pycache__/
3
+
4
+ test-output.xml
5
+ .coverage
6
+ coverage-unittests.xml
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Axa France IARD / Axa France VIE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,658 @@
1
+ Metadata-Version: 2.4
2
+ Name: axa-fr-oidc
3
+ Version: 1.3.0
4
+ Summary: Python library for OpenID Connect (OIDC) authentication with DPoP (Demonstrating Proof-of-Possession) support, featuring JWT validation, token caching, and both sync/async operations.
5
+ Author: Cop Python
6
+ License-File: LICENSE
7
+ Keywords: async,authentication,dpop,jwt,oauth2,oidc,openid-connect,token-validation
8
+ Requires-Python: <3.13,>=3.10
9
+ Requires-Dist: httpx<1.0.0,>=0.28.1
10
+ Requires-Dist: jwskate<1.0.0,>=0.12.2
11
+ Requires-Dist: loguru<1.0.0,>=0.7.3
12
+ Requires-Dist: pyjwt<3.0.0,>=2.10.1
13
+ Requires-Dist: requests-oauth2client<2.0.0,>=1.8.0
14
+ Description-Content-Type: text/markdown
15
+
16
+ # axa-fr-oidc
17
+
18
+ <div align="center">
19
+
20
+ | Python | Project |
21
+ |-------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
22
+ | ![Python Version](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white) | ![Axa France OIDC Badge](https://img.shields.io/badge/Axa_France-OIDC-blue?logo=apachekafka&logoColor=white) |
23
+
24
+ </div>
25
+
26
+ A Python library for OpenID Connect (OIDC) authentication with DPoP (Demonstrating Proof-of-Possession) support, featuring JWT validation, token caching, and both sync/async operations.
27
+
28
+ ## Features
29
+
30
+ - 🔐 **OIDC Authentication** - Full OpenID Connect authentication support
31
+ - 🔑 **DPoP Support** - Demonstrating Proof-of-Possession for enhanced security
32
+ - ✅ **JWT Validation** - Comprehensive token validation with JWKS
33
+ - 💾 **Token Caching** - Built-in memory cache for tokens and JWKS
34
+ - ⚡ **Async/Sync** - Supports both synchronous and asynchronous operations
35
+ - 🎯 **Type Safe** - Fully typed with Python type hints
36
+ - 🔒 **Flexible Auth Methods** - `client_secret_jwt`, `client_secret_post`, and `client_secret_basic` with automatic fallback
37
+
38
+ ## Installation
39
+
40
+ ### Using uv (recommended)
41
+
42
+ ```bash
43
+ uv add axa-fr-oidc
44
+ ```
45
+
46
+ ### Using pip
47
+
48
+ ```bash
49
+ pip install axa-fr-oidc
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ### Simple Usage with OidcClient (Recommended)
55
+
56
+ The `OidcClient` provides a simplified, high-level API for common OIDC operations:
57
+
58
+ ```python
59
+ from axa_fr_oidc import OidcClient
60
+
61
+ # Create a client with client credentials
62
+ client = OidcClient(
63
+ issuer="https://issuer.url",
64
+ client_id="your-client-id",
65
+ client_secret="your-client-secret",
66
+ scopes=["openid", "profile"],
67
+ audience="your-api-audience",
68
+ )
69
+
70
+ # Get an access token (automatically cached and refreshed)
71
+ access_token = client.get_access_token()
72
+
73
+ # Validate a token
74
+ result = client.validate_token(access_token)
75
+ if result.success:
76
+ print(f"Token is valid! Subject: {result.payload['sub']}")
77
+ else:
78
+ print(f"Token is invalid: {result.error}")
79
+
80
+ # Clean up resources
81
+ client.close_sync()
82
+ ```
83
+
84
+ #### Using Context Managers
85
+
86
+ ```python
87
+ from axa_fr_oidc import OidcClient
88
+
89
+ # Sync context manager
90
+ with OidcClient(
91
+ issuer="https://issuer.url",
92
+ client_id="your-client-id",
93
+ client_secret="your-client-secret",
94
+ ) as client:
95
+ token = client.get_access_token()
96
+
97
+ # Async context manager
98
+ async with OidcClient(
99
+ issuer="https://issuer.url",
100
+ client_id="your-client-id",
101
+ client_secret="your-client-secret",
102
+ ) as client:
103
+ token = await client.get_access_token_async()
104
+ ```
105
+
106
+ #### Async Operations
107
+
108
+ ```python
109
+ import asyncio
110
+ from axa_fr_oidc import OidcClient
111
+
112
+ async def main():
113
+ async with OidcClient(
114
+ issuer="https://issuer.url",
115
+ client_id="your-client-id",
116
+ client_secret="your-client-secret",
117
+ ) as client:
118
+ # Async token retrieval
119
+ token = await client.get_access_token_async()
120
+
121
+ # Async token validation
122
+ result = await client.validate_token_async(token)
123
+ print(result.success, result.payload)
124
+
125
+ asyncio.run(main())
126
+ ```
127
+
128
+ #### Private Key Authentication
129
+
130
+ ```python
131
+ from axa_fr_oidc import OidcClient
132
+
133
+ # Load your private key
134
+ with open("private_key.pem", "r") as f:
135
+ private_key_pem = f.read()
136
+
137
+ client = OidcClient(
138
+ issuer="https://issuer.url",
139
+ client_id="your-client-id",
140
+ private_key=private_key_pem,
141
+ algorithm="RS256",
142
+ scopes=["openid", "profile"],
143
+ )
144
+
145
+ token = client.get_access_token()
146
+ ```
147
+
148
+ #### Validating DPoP Tokens
149
+
150
+ ```python
151
+ from axa_fr_oidc import OidcClient
152
+
153
+ client = OidcClient(
154
+ issuer="https://issuer.url",
155
+ client_id="your-client-id",
156
+ )
157
+
158
+ # Validate a DPoP-bound token
159
+ result = client.validate_token(
160
+ token=access_token,
161
+ dpop=dpop_proof,
162
+ path="/api/resource",
163
+ http_method="POST",
164
+ )
165
+
166
+ print(result.success, result.error)
167
+ ```
168
+
169
+ #### Token Exchange
170
+
171
+ ```python
172
+ from axa_fr_oidc import OidcClient
173
+
174
+ client = OidcClient(
175
+ issuer="https://issuer.url",
176
+ client_id="your-client-id",
177
+ client_secret="your-client-secret",
178
+ )
179
+
180
+ # Exchange a token (RFC 8693)
181
+ new_token = client.token_exchange(
182
+ subject_token=user_token,
183
+ requested_token_type="urn:ietf:params:oauth:token-type:access_token",
184
+ )
185
+ ```
186
+
187
+ #### Custom HTTP Configuration (Proxies, SSL, Timeouts)
188
+
189
+ The client supports custom HTTP configurations including proxy settings, SSL verification, and timeouts:
190
+
191
+ ```python
192
+ from axa_fr_oidc import OidcClient
193
+
194
+ # Using a proxy
195
+ client = OidcClient(
196
+ issuer="https://issuer.url",
197
+ client_id="your-client-id",
198
+ client_secret="your-client-secret",
199
+ proxy="http://proxy.example.com:8080",
200
+ )
201
+
202
+ # Using an HTTPS proxy
203
+ client = OidcClient(
204
+ issuer="https://issuer.url",
205
+ client_id="your-client-id",
206
+ client_secret="your-client-secret",
207
+ proxy="https://secure-proxy.example.com:8443",
208
+ )
209
+
210
+ # Disable SSL verification (not recommended for production)
211
+ client = OidcClient(
212
+ issuer="https://issuer.url",
213
+ client_id="your-client-id",
214
+ client_secret="your-client-secret",
215
+ verify=False,
216
+ )
217
+
218
+ # Set custom timeout (in seconds)
219
+ client = OidcClient(
220
+ issuer="https://issuer.url",
221
+ client_id="your-client-id",
222
+ client_secret="your-client-secret",
223
+ timeout=30.0,
224
+ )
225
+
226
+ # Combine multiple HTTP configurations
227
+ client = OidcClient(
228
+ issuer="https://issuer.url",
229
+ client_id="your-client-id",
230
+ client_secret="your-client-secret",
231
+ proxy="http://proxy.example.com:8080",
232
+ verify=True,
233
+ timeout=10.0,
234
+ )
235
+
236
+ token = client.get_access_token()
237
+ ```
238
+
239
+ #### Client Secret Authentication Methods
240
+
241
+ When using `client_secret`, you can control how the credentials are sent to the
242
+ token endpoint via the `auth_method` parameter.
243
+
244
+ | `auth_method` | Behaviour |
245
+ |---|---|
246
+ | `"client_secret_jwt"` *(default)* | Signs an HS256 JWT assertion (RFC 7523). **Automatically falls back to `client_secret_post` on 401** if the server does not have this method enabled for the client. |
247
+ | `"client_secret_post"` | Sends `client_id` + `client_secret` in the POST body. Broadly supported. |
248
+ | `"client_secret_basic"` | Sends credentials as an HTTP Basic Auth header. Broadly supported. |
249
+
250
+ ```python
251
+ from axa_fr_oidc import OidcClient
252
+ from axa_fr_oidc.constants import (
253
+ CLIENT_SECRET_AUTH_METHOD_JWT, # "client_secret_jwt" (default)
254
+ CLIENT_SECRET_AUTH_METHOD_POST, # "client_secret_post"
255
+ CLIENT_SECRET_AUTH_METHOD_BASIC, # "client_secret_basic"
256
+ )
257
+
258
+ # Default: tries client_secret_jwt, falls back to client_secret_post on 401
259
+ client = OidcClient(
260
+ issuer="https://issuer.url",
261
+ client_id="your-client-id",
262
+ client_secret="your-client-secret",
263
+ )
264
+
265
+ # Explicitly use client_secret_post (no fallback overhead)
266
+ client = OidcClient(
267
+ issuer="https://issuer.url",
268
+ client_id="your-client-id",
269
+ client_secret="your-client-secret",
270
+ auth_method=CLIENT_SECRET_AUTH_METHOD_POST,
271
+ )
272
+
273
+ # Explicitly use client_secret_basic
274
+ client = OidcClient(
275
+ issuer="https://issuer.url",
276
+ client_id="your-client-id",
277
+ client_secret="your-client-secret",
278
+ auth_method=CLIENT_SECRET_AUTH_METHOD_BASIC,
279
+ )
280
+
281
+ token = client.get_access_token()
282
+ ```
283
+
284
+ For more details, see the [Client Secret Auth Methods Guide](./docs/client-secret-auth-methods.md).
285
+
286
+ ### Extract Properties from a JWT Token
287
+
288
+ ```python
289
+ from axa_fr_oidc import JWTAuthorization
290
+
291
+ authorization_header = "<your-jwt-token>"
292
+ jwt_auth = JWTAuthorization(authorization_header)
293
+
294
+ print(jwt_auth.get_property("sub")) # Print the subject of the token
295
+ print(jwt_auth.get_property("exp")) # Print the expiration time of the token
296
+ ```
297
+
298
+ ## Advanced Usage (Low-Level API)
299
+
300
+ For users who need more control over the authentication process, the library provides
301
+ low-level components that can be customized individually.
302
+
303
+ ### Using OpenIdConnect and OidcAuthentication Directly
304
+
305
+ ```python
306
+ from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
307
+ from httpx import AsyncClient, Client
308
+
309
+ # Create HTTP clients
310
+ http_client = Client()
311
+ http_async_client = AsyncClient()
312
+
313
+ # Create HTTP service
314
+ http_service = XHttpServiceGet(
315
+ http_client=http_client,
316
+ http_async_client=http_async_client
317
+ )
318
+
319
+ # Create cache
320
+ memory_cache = MemoryCache()
321
+
322
+ # Create authentication handler
323
+ auth = OidcAuthentication(
324
+ issuer="https://issuer.url",
325
+ scopes=["openid", "profile"],
326
+ api_audience="your-api-audience",
327
+ service=http_service,
328
+ memory_cache=memory_cache,
329
+ algorithms=["RS256", "ES256"],
330
+ cache_expiration=7200, # Cache JWKS for 2 hours
331
+ )
332
+
333
+ # Create OpenID Connect client
334
+ oidc = OpenIdConnect(
335
+ authentication=auth,
336
+ memory_cache=memory_cache,
337
+ client_id="your-client-id",
338
+ client_secret="your-client-secret"
339
+ )
340
+
341
+ # Get access token
342
+ access_token = oidc.get_access_token()
343
+
344
+ # Validate token
345
+ result = auth.validate(access_token, None, None, None)
346
+ print(result.success, result.error)
347
+ ```
348
+
349
+ ### Async Operations with Low-Level API
350
+
351
+ All low-level components support async/await:
352
+
353
+ ```python
354
+ from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
355
+ from httpx import AsyncClient, Client
356
+
357
+ async def main():
358
+ http_service = XHttpServiceGet(
359
+ http_client=Client(),
360
+ http_async_client=AsyncClient()
361
+ )
362
+ memory_cache = MemoryCache()
363
+
364
+ auth = OidcAuthentication(
365
+ issuer="https://issuer.url",
366
+ scopes=["openid", "profile"],
367
+ api_audience="your-api-audience",
368
+ service=http_service,
369
+ memory_cache=memory_cache
370
+ )
371
+
372
+ oidc = OpenIdConnect(
373
+ authentication=auth,
374
+ memory_cache=memory_cache,
375
+ client_id="your-client-id",
376
+ client_secret="your-client-secret"
377
+ )
378
+
379
+ # Async token retrieval
380
+ access_token = await oidc.get_access_token_async()
381
+
382
+ # Async token validation
383
+ result = await auth.validate_async(access_token, None, None, None)
384
+ print(result.success, result.payload)
385
+
386
+ # Run with asyncio
387
+ import asyncio
388
+ asyncio.run(main())
389
+ ```
390
+
391
+ ### Using Private Key Authentication (Low-Level)
392
+
393
+ For client credentials flow with private key (JWT bearer) using the low-level API:
394
+
395
+ ```python
396
+ from axa_fr_oidc import OidcAuthentication, OpenIdConnect, MemoryCache, XHttpServiceGet
397
+ from httpx import AsyncClient, Client
398
+
399
+ # Load your private key
400
+ with open("private_key.pem", "r") as f:
401
+ private_key_pem = f.read()
402
+
403
+ http_service = XHttpServiceGet(
404
+ http_client=Client(),
405
+ http_async_client=AsyncClient()
406
+ )
407
+ memory_cache = MemoryCache()
408
+
409
+ auth = OidcAuthentication(
410
+ issuer="https://issuer.url",
411
+ scopes=["openid", "profile"],
412
+ api_audience="your-api-audience",
413
+ service=http_service,
414
+ memory_cache=memory_cache
415
+ )
416
+
417
+ oidc = OpenIdConnect(
418
+ authentication=auth,
419
+ memory_cache=memory_cache,
420
+ client_id="your-client-id",
421
+ private_key=private_key_pem,
422
+ algorithm="RS256" # or other supported algorithms
423
+ )
424
+
425
+ access_token = oidc.get_access_token()
426
+ ```
427
+
428
+ > **Note:** For most use cases, consider using the simpler `OidcClient` instead.
429
+ > See the [Quick Start](#quick-start) section for examples.
430
+
431
+ ### Custom Configuration
432
+
433
+ You can customize various timeouts and cache settings:
434
+
435
+ #### Using OidcClient
436
+
437
+ ```python
438
+ from axa_fr_oidc import OidcClient
439
+
440
+ client = OidcClient(
441
+ issuer="https://issuer.url",
442
+ client_id="your-client-id",
443
+ client_secret="your-client-secret",
444
+ scopes=["openid", "profile"],
445
+ audience="your-api-audience",
446
+ algorithms=["RS256", "ES256"], # Allowed algorithms for validation
447
+ cache_expiration=7200, # Cache JWKS for 2 hours (default: 24 hours)
448
+ )
449
+ ```
450
+
451
+ #### Using Low-Level API
452
+
453
+ ```python
454
+ from axa_fr_oidc import (
455
+ OidcAuthentication,
456
+ MemoryCache,
457
+ XHttpServiceGet,
458
+ )
459
+ from httpx import AsyncClient, Client
460
+
461
+ auth = OidcAuthentication(
462
+ issuer="https://issuer.url",
463
+ scopes=["openid", "profile"],
464
+ api_audience="your-api-audience",
465
+ service=XHttpServiceGet(
466
+ http_client=Client(),
467
+ http_async_client=AsyncClient()
468
+ ),
469
+ memory_cache=MemoryCache(),
470
+ algorithms=["RS256", "ES256"], # Supported algorithms
471
+ cache_expiration=7200, # Cache JWKS for 2 hours (default: 24 hours)
472
+ )
473
+ ```
474
+
475
+ ## API Reference
476
+
477
+ ### High-Level Client (Recommended)
478
+
479
+ - **`OidcClient`** - Simplified, all-in-one client for OIDC operations
480
+ - `get_access_token()` / `get_access_token_async()` - Get an access token
481
+ - `validate_token()` / `validate_token_async()` - Validate an access token
482
+ - `token_exchange()` - Exchange tokens (RFC 8693)
483
+ - `get_token_endpoint()` / `get_token_endpoint_async()` - Get the token endpoint URL
484
+ - `clear_cache()` - Clear all cached data
485
+ - `close()` / `close_sync()` - Release resources
486
+ - Supports context managers (`with`/`async with`)
487
+
488
+ ### Low-Level Classes
489
+
490
+ - **`OidcAuthentication`** - OIDC token validation and JWKS management
491
+ - **`OpenIdConnect`** - Client for obtaining access tokens
492
+ - **`MemoryCache`** - In-memory cache for tokens and JWKS
493
+ - **`XHttpServiceGet`** - HTTP service wrapper for sync/async requests
494
+ - **`JWTAuthorization`** - Utility for extracting JWT claims
495
+ - **`AuthenticationResult`** - Result object from validation operations
496
+
497
+ ### Interfaces
498
+
499
+ All main classes have corresponding interfaces for dependency injection:
500
+
501
+ - **`IOidcAuthentication`** - Interface for OidcAuthentication
502
+ - **`IOpenIdConnect`** - Interface for OpenIdConnect
503
+ - **`IMemoryCache`** - Interface for MemoryCache
504
+ - **`IHttpServiceGet`** - Interface for XHttpServiceGet
505
+ - **`IGenericAuthorization`** - Interface for JWTAuthorization
506
+
507
+ ### Constants
508
+
509
+ The library exports useful constants for configuration:
510
+
511
+ ```python
512
+ from axa_fr_oidc import (
513
+ DEFAULT_CACHE_EXPIRATION_SECONDS, # 86400 (24 hours)
514
+ DEFAULT_DPOP_MAX_AGE_SECONDS, # 300 (5 minutes)
515
+ DEFAULT_CLOCK_SKEW_SECONDS, # 300 (5 minutes)
516
+ DEFAULT_JTI_LIFETIME_SECONDS, # 300 (5 minutes)
517
+ DEFAULT_JWT_ALGORITHM, # "RS256"
518
+ DEFAULT_JWT_EXPIRATION_SECONDS, # 300 (5 minutes)
519
+ DEFAULT_HTTP_TIMEOUT_SECONDS, # 5 seconds
520
+ SUPPORTED_ALGORITHMS, # ["RS256", "HS256"]
521
+ DPOP_TOKEN_TYPE, # "dpop+jwt"
522
+ GRANT_TYPE_CLIENT_CREDENTIALS, # "client_credentials"
523
+ CLIENT_ASSERTION_TYPE_JWT_BEARER, # "urn:ietf:params:oauth:..."
524
+ CONTENT_TYPE_FORM_URLENCODED, # "application/x-www-form-urlencoded"
525
+ OIDC_WELL_KNOWN_PATH, # "/.well-known/openid-configuration"
526
+ CLIENT_SECRET_AUTH_METHOD_JWT, # "client_secret_jwt"
527
+ CLIENT_SECRET_AUTH_METHOD_POST, # "client_secret_post"
528
+ CLIENT_SECRET_AUTH_METHOD_BASIC, # "client_secret_basic"
529
+ )
530
+ ```
531
+
532
+ ## Advanced Configuration
533
+
534
+ ### Client Secret Authentication Methods
535
+
536
+ For detailed information on configuring the client-secret auth method
537
+ (`client_secret_jwt`, `client_secret_post`, `client_secret_basic`) and the
538
+ automatic fallback behaviour, see the
539
+ [Client Secret Auth Methods Guide](./docs/client-secret-auth-methods.md).
540
+
541
+ ### Proxy, SSL, and Timeout Configuration
542
+
543
+ For detailed information on configuring HTTP proxies, SSL verification, and timeouts, see the [Proxy Configuration Guide](./docs/proxy-configuration.md).
544
+
545
+ Quick examples:
546
+
547
+ ```python
548
+ # Using a proxy
549
+ client = OidcClient(
550
+ issuer="https://auth.example.com",
551
+ client_id="your-client-id",
552
+ client_secret="your-client-secret",
553
+ proxy="http://proxy.example.com:8080",
554
+ )
555
+
556
+ # With custom timeout
557
+ client = OidcClient(
558
+ issuer="https://auth.example.com",
559
+ client_id="your-client-id",
560
+ client_secret="your-client-secret",
561
+ timeout=30.0,
562
+ )
563
+ ```
564
+
565
+ ## Development
566
+
567
+ ### Setup Development Environment
568
+
569
+ ```bash
570
+ # Clone the repository
571
+ git clone https://github.com/your-org/axa-fr-oidc.git
572
+ cd axa-fr-oidc
573
+
574
+ # Install uv if not already installed
575
+ curl -LsSf https://astral.sh/uv/install.sh | sh
576
+
577
+ # Install dependencies
578
+ uv sync --group dev
579
+ ```
580
+
581
+ ### Using the Makefile
582
+
583
+ The project includes a `Makefile` for convenient development commands:
584
+
585
+ ```bash
586
+ # Show all available commands
587
+ make help
588
+
589
+ # Install dependencies
590
+ make install # Production dependencies only
591
+ make install-dev # All development dependencies
592
+ make install-quality # Quality check tools only
593
+ make install-test # Test dependencies only
594
+
595
+ # Code quality
596
+ make lint # Run ruff linter (includes docstring checks)
597
+ make lint-fix # Run ruff linter with auto-fix
598
+ make format # Run ruff formatter
599
+ make format-check # Check formatting without changes
600
+ make type-check # Run mypy type checker
601
+
602
+ # Security
603
+ make security # Run bandit security checks
604
+ make security-audit # Run pip-audit for dependency vulnerabilities
605
+
606
+ # Testing
607
+ make test # Run tests
608
+ make test-cov # Run tests with coverage report
609
+
610
+ # Combined commands
611
+ make quality # Run all quality checks (lint, format, type-check, security)
612
+ make all # Run quality checks and tests
613
+
614
+ # Cleanup
615
+ make clean # Remove build artifacts and cache files
616
+ ```
617
+
618
+ ### Running Tests
619
+
620
+ ```bash
621
+ # Using make
622
+ make test
623
+
624
+ # Or directly with uv
625
+ uv run pytest
626
+ ```
627
+
628
+ ### Running Quality Checks
629
+
630
+ ```bash
631
+ # Run all quality checks at once
632
+ make quality
633
+
634
+ # Or run individual checks
635
+ make lint
636
+ make type-check
637
+ make security
638
+ ```
639
+
640
+ ### Installing Specific Dependency Groups
641
+
642
+ ```bash
643
+ # Install only test dependencies
644
+ uv sync --group test
645
+
646
+ # Install only linting tools
647
+ uv sync --group lint
648
+
649
+ # Install only security tools
650
+ uv sync --group security
651
+
652
+ # Install everything for development
653
+ uv sync --group dev
654
+ ```
655
+
656
+ ## Contributing
657
+
658
+ Contributions are welcome! Please feel free to submit a Pull Request.