reqkey 0.1.0__py3-none-any.whl

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,679 @@
1
+ Metadata-Version: 2.4
2
+ Name: reqkey
3
+ Version: 0.1.0
4
+ Summary: The official Python SDK for ReqKey API key validation and usage analytics
5
+ Project-URL: Homepage, https://reqkey.com
6
+ Project-URL: Documentation, https://reqkey.com/docs
7
+ Project-URL: Repository, https://github.com/Req-Key/reqkey-python
8
+ Project-URL: Issues, https://github.com/Req-Key/reqkey-python/issues
9
+ Author-email: ReqKey <support@reqkey.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 ReqKey
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+
32
+ License-File: LICENSE
33
+ Keywords: api-keys,authentication,fastapi,rate-limiting,usage-based-billing
34
+ Classifier: Development Status :: 3 - Alpha
35
+ Classifier: Framework :: Django
36
+ Classifier: Framework :: FastAPI
37
+ Classifier: Framework :: Flask
38
+ Classifier: Intended Audience :: Developers
39
+ Classifier: License :: OSI Approved :: MIT License
40
+ Classifier: Programming Language :: Python :: 3
41
+ Classifier: Programming Language :: Python :: 3.10
42
+ Classifier: Programming Language :: Python :: 3.11
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Programming Language :: Python :: 3.13
45
+ Classifier: Programming Language :: Python :: 3.14
46
+ Classifier: Typing :: Typed
47
+ Requires-Python: >=3.10
48
+ Requires-Dist: httpx<1,>=0.27
49
+ Provides-Extra: all
50
+ Requires-Dist: django<6.1,>=5.2; extra == 'all'
51
+ Requires-Dist: fastapi<1,>=0.110; extra == 'all'
52
+ Requires-Dist: flask<4,>=3; extra == 'all'
53
+ Provides-Extra: asgi
54
+ Requires-Dist: starlette<1,>=0.37; extra == 'asgi'
55
+ Provides-Extra: dev
56
+ Requires-Dist: build>=1.2; extra == 'dev'
57
+ Requires-Dist: django-stubs<7,>=6.0.7; extra == 'dev'
58
+ Requires-Dist: django<6.1,>=5.2; extra == 'dev'
59
+ Requires-Dist: fastapi<1,>=0.110; extra == 'dev'
60
+ Requires-Dist: flask<4,>=3; extra == 'dev'
61
+ Requires-Dist: mypy>=1.13; extra == 'dev'
62
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
63
+ Requires-Dist: pytest>=8.0; extra == 'dev'
64
+ Requires-Dist: ruff>=0.8; extra == 'dev'
65
+ Provides-Extra: django
66
+ Requires-Dist: django<6.1,>=5.2; extra == 'django'
67
+ Provides-Extra: fastapi
68
+ Requires-Dist: fastapi<1,>=0.110; extra == 'fastapi'
69
+ Provides-Extra: flask
70
+ Requires-Dist: flask<4,>=3; extra == 'flask'
71
+ Provides-Extra: wsgi
72
+ Description-Content-Type: text/markdown
73
+
74
+ # ReqKey Python SDK
75
+
76
+ The official Python SDK for API key validation, credit metering, consumer rate
77
+ limits, and correlated API traffic analytics with ReqKey.
78
+
79
+ One distribution contains the shared sync/async client and optional framework
80
+ adapters. FastAPI, Starlette, Django, Flask, and generic ASGI/WSGI applications
81
+ can all use ready-made middleware without implementing ReqKey request logic.
82
+
83
+ ## Supported integrations
84
+
85
+ | Application | Install | Middleware import |
86
+ |---|---|---|
87
+ | FastAPI / Starlette | `reqkey[fastapi]` | `reqkey.fastapi.ReqKeyMiddleware` |
88
+ | Generic ASGI | `reqkey[asgi]` | `reqkey.asgi.ReqKeyMiddleware` |
89
+ | Django (sync or async) | `reqkey[django]` | `reqkey.django.ReqKeyMiddleware` |
90
+ | Flask | `reqkey[flask]` | `reqkey.flask.ReqKeyMiddleware` |
91
+ | Bottle / Pyramid / generic WSGI | `reqkey` or `reqkey[wsgi]` | `reqkey.wsgi.ReqKeyMiddleware` |
92
+ | Scripts, workers, and custom frameworks | `reqkey` | `reqkey.ReqKey` / `reqkey.AsyncReqKey` |
93
+
94
+ Framework dependencies are optional. Installing plain `reqkey` installs only
95
+ the universal client and its HTTP dependency; it does not install Django,
96
+ Flask, FastAPI, or Starlette.
97
+
98
+ This release provides ready-made middleware for incoming requests in the
99
+ frameworks listed above. It does not yet include native Tornado or AWS Lambda
100
+ adapters, nor automatic instrumentation of outgoing calls made with `requests`
101
+ or `httpx`. Those applications can still use the direct `ReqKey` and
102
+ `AsyncReqKey` clients, but framework-specific request extraction remains
103
+ application code until a dedicated adapter is added.
104
+
105
+ ## Install
106
+
107
+ Core client:
108
+
109
+ ```bash
110
+ pip install reqkey
111
+ ```
112
+
113
+ FastAPI middleware:
114
+
115
+ ```bash
116
+ pip install "reqkey[fastapi]"
117
+ ```
118
+
119
+ Django middleware:
120
+
121
+ ```bash
122
+ pip install "reqkey[django]"
123
+ ```
124
+
125
+ Flask middleware:
126
+
127
+ ```bash
128
+ pip install "reqkey[flask]"
129
+ ```
130
+
131
+ Every supported framework integration:
132
+
133
+ ```bash
134
+ pip install "reqkey[all]"
135
+ ```
136
+
137
+ ## Complete FastAPI integration
138
+
139
+ ```python
140
+ import os
141
+
142
+ from fastapi import FastAPI, Request
143
+ from reqkey.fastapi import ReqKeyMiddleware
144
+
145
+ app = FastAPI()
146
+
147
+ app.add_middleware(
148
+ ReqKeyMiddleware,
149
+
150
+ # ReqKey project
151
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
152
+ api_id="api_payments",
153
+
154
+ # "validate", "ingest", or "both"
155
+ mode="both",
156
+ enabled=True,
157
+
158
+ # Where your consumer sends their ReqKey-issued key
159
+ key_location="header",
160
+ key_name="X-StartupName-Key",
161
+ key_scheme="raw",
162
+
163
+ # Usage cost
164
+ credits=1,
165
+
166
+ # No validation or analytics on these routes
167
+ exclude_paths=("/health", "/docs", "/openapi.json", "/redoc", "/cron/*"),
168
+
169
+ # Privacy-safe analytics defaults
170
+ capture_query_params=False,
171
+ capture_request_headers=False,
172
+ capture_response_headers=False,
173
+ capture_response_body=False,
174
+ )
175
+
176
+
177
+ @app.get("/health")
178
+ async def health() -> dict[str, bool]:
179
+ return {"ok": True}
180
+
181
+
182
+ @app.post("/payments")
183
+ async def create_payment(request: Request) -> dict[str, object]:
184
+ decision = request.state.reqkey
185
+ return {
186
+ "created": True,
187
+ "credits_remaining": decision.credits_remaining,
188
+ }
189
+ ```
190
+
191
+ The application contains no direct `/key/validate` or `/ingest` requests. The
192
+ middleware owns that internal work.
193
+
194
+ ## Complete Django integration
195
+
196
+ Set the project credential in the server environment:
197
+
198
+ ```bash
199
+ export REQKEY_PROJECT_KEY="your_project_key"
200
+ ```
201
+
202
+ Then configure `settings.py`:
203
+
204
+ ```python
205
+ REQKEY = {
206
+ "API_ID": "api_payments",
207
+ "MODE": "both",
208
+ "KEY_LOCATION": "header",
209
+ "KEY_NAME": "X-StartupName-Key",
210
+ "KEY_SCHEME": "raw",
211
+ "CREDITS": 1,
212
+ "EXCLUDE_PATHS": ("/health", "/admin/*", "/static/*"),
213
+ "CAPTURE_QUERY_PARAMS": False,
214
+ "CAPTURE_REQUEST_HEADERS": False,
215
+ "CAPTURE_RESPONSE_HEADERS": False,
216
+ "CAPTURE_RESPONSE_BODY": False,
217
+ }
218
+
219
+ MIDDLEWARE = [
220
+ "django.middleware.security.SecurityMiddleware",
221
+ "reqkey.django.ReqKeyMiddleware",
222
+ # The rest of your middleware...
223
+ ]
224
+ ```
225
+
226
+ The same middleware class detects whether Django supplied a synchronous or
227
+ asynchronous request chain and uses `ReqKey` or `AsyncReqKey` accordingly.
228
+ After successful validation, a Django view can read:
229
+
230
+ ```python
231
+ def create_payment(request):
232
+ decision = request.reqkey
233
+ return JsonResponse(
234
+ {
235
+ "created": True,
236
+ "credits_remaining": decision.credits_remaining,
237
+ }
238
+ )
239
+ ```
240
+
241
+ All direct middleware keyword options are available in Django's `REQKEY`
242
+ mapping using uppercase names. `PROJECT_KEY` may be specified there, but an
243
+ environment variable is recommended so credentials do not enter source
244
+ control.
245
+
246
+ ## Complete Flask integration
247
+
248
+ ```python
249
+ import os
250
+
251
+ from flask import Flask, request
252
+ from reqkey.flask import ReqKeyMiddleware
253
+
254
+ app = Flask(__name__)
255
+ app.wsgi_app = ReqKeyMiddleware(
256
+ app.wsgi_app,
257
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
258
+ api_id="api_payments",
259
+ mode="both",
260
+ key_name="X-StartupName-Key",
261
+ exclude_paths=("/health", "/static/*"),
262
+ )
263
+
264
+
265
+ @app.post("/payments")
266
+ def create_payment():
267
+ decision = request.environ["reqkey.decision"]
268
+ return {
269
+ "created": True,
270
+ "credits_remaining": decision.credits_remaining,
271
+ }
272
+ ```
273
+
274
+ Flask remains the application object; only its `wsgi_app` callable is wrapped.
275
+ The same `reqkey.wsgi.ReqKeyMiddleware` class works with Bottle, Pyramid, and
276
+ other PEP 3333 WSGI applications.
277
+
278
+ ## Generic ASGI and WSGI
279
+
280
+ For any ASGI 3 application:
281
+
282
+ ```python
283
+ import os
284
+
285
+ from reqkey.asgi import ReqKeyMiddleware
286
+
287
+ application = ReqKeyMiddleware(
288
+ application,
289
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
290
+ api_id="api_payments",
291
+ )
292
+ ```
293
+
294
+ For any WSGI application:
295
+
296
+ ```python
297
+ import os
298
+
299
+ from reqkey.wsgi import ReqKeyMiddleware
300
+
301
+ application = ReqKeyMiddleware(
302
+ application,
303
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
304
+ api_id="api_payments",
305
+ )
306
+ ```
307
+
308
+ ## Request lifecycle
309
+
310
+ With `mode="both"`:
311
+
312
+ ```text
313
+ Consumer request
314
+ → extract consumer key
315
+ → await /key/validate
316
+ → denied: return 401 / 402 / 403 / 429
317
+ → approved: run the FastAPI endpoint
318
+ → collect response metadata
319
+ → await /ingest with the validation requestId
320
+ → release the response to the consumer
321
+ ```
322
+
323
+ The framework adapters keep ingestion inside the request lifecycle instead of
324
+ starting an unreliable background task:
325
+
326
+ - ASGI awaits ingestion before sending the final response-completion message.
327
+ - WSGI ingests when the response iterable finishes, without buffering the full
328
+ response.
329
+ - Django's native middleware ingests before returning the response object.
330
+
331
+ Streaming responses are forwarded chunk by chunk. The middleware keeps only
332
+ enough data to reconstruct the first 1,000 response-body characters and holds
333
+ the final ASGI completion message until ingestion finishes. It does not retain
334
+ the complete stream in memory. An indefinitely open stream cannot produce a
335
+ completed analytics event until that stream eventually closes.
336
+
337
+ Django streaming responses are never consumed or buffered by the native
338
+ middleware. Their status and headers can be recorded, but response-body capture
339
+ is omitted because Django requires middleware to preserve the streaming
340
+ iterator.
341
+
342
+ ## Configuration
343
+
344
+ | Input | Default | Purpose |
345
+ |---|---:|---|
346
+ | `project_key` | required | Project credential sent to ReqKey as the Bearer token. |
347
+ | `root_key` | — | Backward-compatible alias for `project_key`; never pass both. |
348
+ | `api_id` | required | ReqKey API being protected or observed. |
349
+ | `mode` | `"both"` | `"validate"`, `"ingest"`, or `"both"`. |
350
+ | `enabled` | `True` | Bypass the integration entirely when false. |
351
+ | `key_location` | `"header"` | `"header"`, `"query"`, or `"cookie"`. |
352
+ | `key_name` | `"X-API-Key"` | Customer-facing header, query parameter, or cookie name. |
353
+ | `key_scheme` | `"raw"` | `"raw"` or `"bearer"` for headers. |
354
+ | `credits` | `1` | Static cost or sync/async cost resolver. |
355
+ | `exclude_paths` | empty | Exact paths or trailing-`*` prefix patterns. |
356
+ | `skip_methods` | `("OPTIONS",)` | Methods that bypass ReqKey. |
357
+ | `failure_mode` | `"closed"` | Deny or allow when ReqKey itself is unavailable. |
358
+ | `error_messages` | built in | Override customer-facing denial messages. |
359
+ | `timeout` | `2.0` | Timeout for each ReqKey operation. |
360
+
361
+ `REQKEY_PROJECT_KEY` is the preferred environment-variable name.
362
+ `REQKEY_ROOT_KEY` remains supported by `ReqKey.from_env()` and
363
+ `AsyncReqKey.from_env()`.
364
+
365
+ The project credential must remain server-side. Do not expose it in browser
366
+ code or commit it to source control.
367
+
368
+ For a local-development switch, pass a boolean without changing the middleware
369
+ layout:
370
+
371
+ ```python
372
+ enabled=os.getenv("ENABLE_REQKEY", "true").lower() == "true"
373
+ ```
374
+
375
+ ## Choose validation, analytics, or both
376
+
377
+ Validation only:
378
+
379
+ ```python
380
+ app.add_middleware(
381
+ ReqKeyMiddleware,
382
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
383
+ api_id="api_payments",
384
+ mode="validate",
385
+ )
386
+ ```
387
+
388
+ Traffic analytics only:
389
+
390
+ ```python
391
+ app.add_middleware(
392
+ ReqKeyMiddleware,
393
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
394
+ api_id="api_payments",
395
+ mode="ingest",
396
+ )
397
+ ```
398
+
399
+ In ingest-only mode, the endpoint runs without ReqKey authentication and the
400
+ event is associated with `api_id`. If another middleware already performed
401
+ validation, provide its request ID for consumer correlation:
402
+
403
+ ```python
404
+ app.add_middleware(
405
+ ReqKeyMiddleware,
406
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
407
+ api_id="api_payments",
408
+ mode="ingest",
409
+ request_id_resolver=lambda request: getattr(
410
+ request.state,
411
+ "reqkey_request_id",
412
+ None,
413
+ ),
414
+ )
415
+ ```
416
+
417
+ Validation plus correlated analytics:
418
+
419
+ ```python
420
+ app.add_middleware(
421
+ ReqKeyMiddleware,
422
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
423
+ api_id="api_payments",
424
+ mode="both",
425
+ )
426
+ ```
427
+
428
+ ## Choose where the consumer key comes from
429
+
430
+ Custom header, recommended:
431
+
432
+ ```python
433
+ key_location="header"
434
+ key_name="X-StartupName-Key"
435
+ key_scheme="raw"
436
+ ```
437
+
438
+ Authorization Bearer token:
439
+
440
+ ```python
441
+ key_location="header"
442
+ key_name="Authorization"
443
+ key_scheme="bearer"
444
+ ```
445
+
446
+ Query parameter:
447
+
448
+ ```python
449
+ key_location="query"
450
+ key_name="api_key"
451
+ ```
452
+
453
+ Query parameters are supported for compatibility, but headers are recommended
454
+ because URLs are often retained in browser history, access logs, and proxy logs.
455
+
456
+ Cookie:
457
+
458
+ ```python
459
+ key_location="cookie"
460
+ key_name="startup_key"
461
+ ```
462
+
463
+ A custom sync or async extractor can handle another source:
464
+
465
+ ```python
466
+ async def get_consumer_key(request: Request) -> str | None:
467
+ return request.headers.get("X-Custom-Key")
468
+
469
+
470
+ app.add_middleware(
471
+ ReqKeyMiddleware,
472
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
473
+ api_id="api_payments",
474
+ get_consumer_key=get_consumer_key,
475
+ )
476
+ ```
477
+
478
+ Avoid reading an API key from the request body. Authentication middleware runs
479
+ before the endpoint and consuming the body can interfere with downstream body
480
+ parsing.
481
+
482
+ ## Exclude or select endpoints
483
+
484
+ Exact and prefix exclusions:
485
+
486
+ ```python
487
+ exclude_paths=(
488
+ "/health",
489
+ "/openapi.json",
490
+ "/docs/*",
491
+ "/cron/*",
492
+ )
493
+ ```
494
+
495
+ For complete control, use a sync or async resolver:
496
+
497
+ ```python
498
+ def should_protect(request: Request) -> bool:
499
+ return request.url.path.startswith("/api/")
500
+
501
+
502
+ app.add_middleware(
503
+ ReqKeyMiddleware,
504
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
505
+ api_id="api_payments",
506
+ should_protect=should_protect,
507
+ )
508
+ ```
509
+
510
+ The same selection controls validation and ingestion. Excluded traffic is not
511
+ validated, charged, or recorded.
512
+
513
+ ## Dynamic credit costs
514
+
515
+ ```python
516
+ def credits_for(request: Request) -> int:
517
+ if request.method == "POST" and request.url.path == "/images":
518
+ return 5
519
+ if request.url.path.startswith("/reports/"):
520
+ return 2
521
+ return 1
522
+
523
+
524
+ app.add_middleware(
525
+ ReqKeyMiddleware,
526
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
527
+ api_id="api_payments",
528
+ credits=credits_for,
529
+ )
530
+ ```
531
+
532
+ ## Analytics capture
533
+
534
+ Metadata sent by default:
535
+
536
+ - `requestId` when validation produced one
537
+ - `apiId`
538
+ - method and normalized endpoint path
539
+ - response status
540
+ - endpoint processing latency
541
+ - user agent
542
+
543
+ Additional data is opt-in:
544
+
545
+ ```python
546
+ app.add_middleware(
547
+ ReqKeyMiddleware,
548
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
549
+ api_id="api_payments",
550
+ mode="both",
551
+ capture_query_params=True,
552
+ capture_request_headers=True,
553
+ capture_response_headers=True,
554
+ capture_response_body=True,
555
+ capture_client_ip=True,
556
+ excluded_headers=(
557
+ "X-RapidAPI-Proxy-Secret",
558
+ "X-Vercel-OIDC-Token",
559
+ ),
560
+ )
561
+ ```
562
+
563
+ Authorization, cookies, `Set-Cookie`, common API-key headers, and the configured
564
+ consumer-key header are always removed from captured headers. Response-body
565
+ capture is limited to textual content and a hard maximum of 1,000 characters.
566
+ The limit is enforced again by the HTTP client before `/ingest` is called.
567
+
568
+ ## Custom denial messages
569
+
570
+ ```python
571
+ app.add_middleware(
572
+ ReqKeyMiddleware,
573
+ project_key=os.environ["REQKEY_PROJECT_KEY"],
574
+ api_id="api_payments",
575
+ error_messages={
576
+ "missing_api_key": "Send your key in X-StartupName-Key.",
577
+ "invalid_api_key": "That key is invalid or disabled.",
578
+ "insufficient_credits": "Your account has no credits remaining.",
579
+ "access_denied": "This key cannot access the requested API.",
580
+ "rate_limited": "Too many requests. Please retry shortly.",
581
+ "reqkey_unavailable": "Authentication is temporarily unavailable.",
582
+ },
583
+ )
584
+ ```
585
+
586
+ The middleware returns stable error identifiers alongside the messages and
587
+ preserves `Retry-After` for rate-limited decisions.
588
+
589
+ ## Request state and response headers
590
+
591
+ After successful validation, FastAPI and Starlette routes can access:
592
+
593
+ ```python
594
+ request.state.reqkey
595
+ request.state.reqkey_request_id
596
+ ```
597
+
598
+ Django views receive the same values as request attributes:
599
+
600
+ ```python
601
+ request.reqkey
602
+ request.reqkey_request_id
603
+ ```
604
+
605
+ WSGI applications, including Flask, receive namespaced environment values:
606
+
607
+ ```python
608
+ request.environ["reqkey.decision"]
609
+ request.environ["reqkey.request_id"]
610
+ ```
611
+
612
+ The response includes these values when available:
613
+
614
+ - `X-ReqKey-Request-ID`
615
+ - `X-ReqKey-Credits-Limit`
616
+ - `X-ReqKey-Credits-Remaining`
617
+ - `X-ReqKey-Validation-Time-Ms`
618
+
619
+ If browser JavaScript needs these headers, add them to the FastAPI CORS
620
+ middleware's `expose_headers` list.
621
+
622
+ ## Availability behavior
623
+
624
+ Validation fails closed by default. When ReqKey times out or cannot be reached,
625
+ the middleware returns `503` without running the endpoint.
626
+
627
+ ```python
628
+ failure_mode="open"
629
+ ```
630
+
631
+ Fail-open applies only to ReqKey transport/service errors. Explicitly invalid,
632
+ disabled, exhausted, forbidden, and rate-limited keys are always denied. In
633
+ `mode="both"`, fail-open requests can still produce an API-level traffic event,
634
+ but it will not be correlated to a validated consumer.
635
+
636
+ The SDK does not automatically retry `/key/validate`, because validation can
637
+ deduct credits. Safe automatic retries require server-side idempotency.
638
+
639
+ ## Direct sync and async clients
640
+
641
+ Async:
642
+
643
+ ```python
644
+ from reqkey import AsyncReqKey
645
+
646
+ async with AsyncReqKey(project_key=os.environ["REQKEY_PROJECT_KEY"]) as reqkey:
647
+ decision = await reqkey.verify(
648
+ "consumer_key_...",
649
+ api_id="api_payments",
650
+ credits=1,
651
+ resource="/payments",
652
+ )
653
+ ```
654
+
655
+ Synchronous:
656
+
657
+ ```python
658
+ from reqkey import ReqKey
659
+
660
+ with ReqKey(project_key=os.environ["REQKEY_PROJECT_KEY"]) as reqkey:
661
+ decision = reqkey.verify("consumer_key_...", api_id="api_payments")
662
+ ```
663
+
664
+ ASGI and FastAPI use the asynchronous client and await both network operations.
665
+ WSGI uses the synchronous client. Django automatically selects the correct
666
+ client based on its middleware chain, avoiding blocking HTTP calls inside an
667
+ asynchronous Django deployment.
668
+
669
+ ## Development
670
+
671
+ ```bash
672
+ python -m venv .venv
673
+ . .venv/bin/activate
674
+ pip install -e ".[dev]"
675
+ ruff check .
676
+ mypy src/reqkey
677
+ pytest
678
+ python -m build
679
+ ```
@@ -0,0 +1,15 @@
1
+ reqkey/__init__.py,sha256=YzNF3bU-WKMtBEwNXWMGqouH9WAGkaTmU7GZWTkPKF4,601
2
+ reqkey/_middleware.py,sha256=ytBYYjHzEarvCpUkOFTOOS69WAwv9xA0qIxxIRwKQhM,4620
3
+ reqkey/asgi.py,sha256=HIKr-J6ajG8fDrjRWnY1DhENACQ01MMy5E3GdCGcvk8,23207
4
+ reqkey/client.py,sha256=iGaPS2BHrGPxWRp3t0ExDJVqH5GQQiyhatMrID2uHNI,16662
5
+ reqkey/django.py,sha256=WpqK2rWCEzqSP4Fm4fhzechz8JSw41_stI9mCKXWIzA,24331
6
+ reqkey/exceptions.py,sha256=ate0koxfy0_Em-a_-T1naNIqHVzV4psHXcbp1SZAqd4,1055
7
+ reqkey/fastapi.py,sha256=qiVEMp0yW1E_I6o-owPp7Jm5OI-pEW7BTZhXqlSe6K4,176
8
+ reqkey/flask.py,sha256=CjQXRXphK7EWe79uhhnnqsLICNJ6ZRmYRGYe-qWFuBA,178
9
+ reqkey/models.py,sha256=YCh5TibeuJpKjs1WaD8PzvRjZdiVvMP4K7b0Ci9nbps,1218
10
+ reqkey/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
+ reqkey/wsgi.py,sha256=qYrDJ3RYWZHMdsTosNnd3opOVCzqMud0xHyqUL9eHDo,19662
12
+ reqkey-0.1.0.dist-info/METADATA,sha256=136QjWO2jv5fk-PNZlyNiXtptd2dGPhlmLi4uD7AEnY,19053
13
+ reqkey-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
14
+ reqkey-0.1.0.dist-info/licenses/LICENSE,sha256=bqplxNJLbhM3cn-TDm1LL73gTUdjBdNkot8KYUvzXkk,1064
15
+ reqkey-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any