csu 2.3.6__tar.gz → 2.4.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.
- {csu-2.3.6 → csu-2.4.0}/.bumpversion.cfg +1 -1
- {csu-2.3.6 → csu-2.4.0}/.cookiecutterrc +1 -1
- {csu-2.3.6 → csu-2.4.0}/PKG-INFO +3 -3
- {csu-2.3.6 → csu-2.4.0}/README.rst +2 -2
- {csu-2.3.6 → csu-2.4.0}/pyproject.toml +1 -1
- {csu-2.3.6 → csu-2.4.0}/src/csu/__init__.py +1 -1
- {csu-2.3.6 → csu-2.4.0}/src/csu/service.py +42 -37
- {csu-2.3.6 → csu-2.4.0}/src/csu.egg-info/PKG-INFO +3 -3
- {csu-2.3.6 → csu-2.4.0}/src/csu.egg-info/SOURCES.txt +2 -0
- csu-2.4.0/tests/cassettes/test_service_auth/test_async_auth.yaml +110 -0
- csu-2.4.0/tests/cassettes/test_service_auth/test_sync_auth.yaml +146 -0
- {csu-2.3.6 → csu-2.4.0}/tests/test_service.py +15 -36
- {csu-2.3.6 → csu-2.4.0}/tests/test_service_auth.py +102 -0
- {csu-2.3.6 → csu-2.4.0}/.coveragerc +0 -0
- {csu-2.3.6 → csu-2.4.0}/.editorconfig +0 -0
- {csu-2.3.6 → csu-2.4.0}/.github/workflows/github-actions.yml +0 -0
- {csu-2.3.6 → csu-2.4.0}/.pre-commit-config.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/.tombi.toml +0 -0
- {csu-2.3.6 → csu-2.4.0}/AUTHORS.rst +0 -0
- {csu-2.3.6 → csu-2.4.0}/CHANGELOG.rst +0 -0
- {csu-2.3.6 → csu-2.4.0}/CONTRIBUTING.rst +0 -0
- {csu-2.3.6 → csu-2.4.0}/LICENSE +0 -0
- {csu-2.3.6 → csu-2.4.0}/MANIFEST.in +0 -0
- {csu-2.3.6 → csu-2.4.0}/ci/bootstrap.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/ci/requirements.txt +0 -0
- {csu-2.3.6 → csu-2.4.0}/ci/templates/.github/workflows/github-actions.yml +0 -0
- {csu-2.3.6 → csu-2.4.0}/pytest.ini +0 -0
- {csu-2.3.6 → csu-2.4.0}/setup.cfg +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/admin.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/conf.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/consts.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/auth.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/fields.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/forms.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/perms.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/phonenumber.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/serializers.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/test_auth.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/test_fields.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/test_forms.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/test_phonenumber.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/test_serializers.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/drf/views.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/enums.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/env.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/exceptions.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/export.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/fixups.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/forms/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/forms/crispy.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/forms/fields.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/gettext.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/gettext_lazy.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/logging.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/management.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/models.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/query.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/routers.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/templates/api_exception.html +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_consts.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_env.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_exceptions.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_logging.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_management.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_service.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_timezones.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_utils.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/test_xml.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/timezones.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/utils.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/views.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/admin.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/asgi.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/engine.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/enums.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/job.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/models.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/worker/registry.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/wsgi.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu/xml.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu.egg-info/dependency_links.txt +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu.egg-info/requires.txt +0 -0
- {csu-2.3.6 → csu-2.4.0}/src/csu.egg-info/top_level.txt +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_decoding.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_error.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_logging.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service/test_redirects.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/conftest.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/exampleworker.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/test_exampleworker.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/test_models.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/test_views.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/test_worker.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/testproject/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/testproject/fixtures/testuser.json +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/testproject/models.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/testproject/settings.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tests/testproject/urls.py +0 -0
- {csu-2.3.6 → csu-2.4.0}/tox.ini +0 -0
{csu-2.3.6 → csu-2.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
|
|
5
5
|
Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
|
|
6
6
|
License-Expression: BSD-2-Clause
|
|
@@ -69,9 +69,9 @@ Overview
|
|
|
69
69
|
.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
|
|
70
70
|
:alt: Supported implementations
|
|
71
71
|
:target: https://pypi.org/project/csu
|
|
72
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.
|
|
72
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.4.0.svg
|
|
73
73
|
:alt: Commits since latest release
|
|
74
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
74
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.4.0...main
|
|
75
75
|
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
|
|
76
76
|
:alt: Scrutinizer Status
|
|
77
77
|
:target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
|
|
@@ -34,9 +34,9 @@ Overview
|
|
|
34
34
|
.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
|
|
35
35
|
:alt: Supported implementations
|
|
36
36
|
:target: https://pypi.org/project/csu
|
|
37
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.
|
|
37
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.4.0.svg
|
|
38
38
|
:alt: Commits since latest release
|
|
39
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
39
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.4.0...main
|
|
40
40
|
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
|
|
41
41
|
:alt: Scrutinizer Status
|
|
42
42
|
:target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
|
|
@@ -215,19 +215,14 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
215
215
|
else:
|
|
216
216
|
self.logger.warning("[%s] __exit__ !> %r", self.short_id, exc_val)
|
|
217
217
|
|
|
218
|
-
def handle_process_response(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if response.status_code in retry_statuses:
|
|
227
|
-
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
228
|
-
elif response.status_code not in accept_statuses:
|
|
229
|
-
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
230
|
-
|
|
218
|
+
def handle_process_response(self, response: Response, *, expect_json: bool) -> RTYPE:
|
|
219
|
+
if expect_json:
|
|
220
|
+
try:
|
|
221
|
+
json = response.json(parse_float=Decimal)
|
|
222
|
+
except Exception as exc:
|
|
223
|
+
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
224
|
+
else:
|
|
225
|
+
json = None
|
|
231
226
|
return HTTPServiceResponse(
|
|
232
227
|
status=response.status_code,
|
|
233
228
|
http_response=response,
|
|
@@ -300,14 +295,11 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
300
295
|
try:
|
|
301
296
|
self.log_request(prepared_request)
|
|
302
297
|
response = self.client.send(prepared_request, timeout=self.client.timeout, allow_redirects=follow_redirects, **settings)
|
|
303
|
-
if
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
else:
|
|
309
|
-
json = None
|
|
310
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
298
|
+
if response.status_code in retry_statuses:
|
|
299
|
+
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
300
|
+
elif response.status_code not in accept_statuses:
|
|
301
|
+
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
302
|
+
return self.handle_process_response(response, expect_json=expect_json)
|
|
311
303
|
except (RequestError, RetryableError) as exc:
|
|
312
304
|
retry_failures.append(exc)
|
|
313
305
|
self.handle_retriable_failure(method, prepared_request.url, exc, retries_left, retry_budget)
|
|
@@ -321,7 +313,7 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
321
313
|
raise ExhaustedRetriesError(retry_budget, retry_failures, event_id=self.event_id)
|
|
322
314
|
|
|
323
315
|
|
|
324
|
-
class AsyncHTTPServiceContext[
|
|
316
|
+
class AsyncHTTPServiceContext[RTYPE = HTTPServiceResponse](HTTPServiceContext[RTYPE]):
|
|
325
317
|
auth_class = AsyncHTTPServiceAuth
|
|
326
318
|
client_class = AsyncSession
|
|
327
319
|
client: AsyncSession
|
|
@@ -345,6 +337,24 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
345
337
|
await self.client.__aexit__(exc_type, exc_val, exc_tb)
|
|
346
338
|
self.handle_exit(exc_type, exc_val, exc_tb)
|
|
347
339
|
|
|
340
|
+
async def handle_process_response(self, response: Response, *, expect_json: bool) -> RTYPE:
|
|
341
|
+
if expect_json:
|
|
342
|
+
try:
|
|
343
|
+
if isinstance(response, AsyncResponse):
|
|
344
|
+
json = await response.json(parse_float=Decimal)
|
|
345
|
+
else:
|
|
346
|
+
json = response.json(parse_float=Decimal)
|
|
347
|
+
except Exception as exc:
|
|
348
|
+
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
349
|
+
else:
|
|
350
|
+
json = None
|
|
351
|
+
return HTTPServiceResponse(
|
|
352
|
+
status=response.status_code,
|
|
353
|
+
http_response=response,
|
|
354
|
+
json=json,
|
|
355
|
+
event_id=self.event_id,
|
|
356
|
+
)
|
|
357
|
+
|
|
348
358
|
async def request(
|
|
349
359
|
self,
|
|
350
360
|
method,
|
|
@@ -360,7 +370,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
360
370
|
verify: TLSVerifyType | None = None,
|
|
361
371
|
cert: TLSClientCertType | None = None,
|
|
362
372
|
**kwargs,
|
|
363
|
-
) ->
|
|
373
|
+
) -> RTYPE:
|
|
364
374
|
retry_statuses = set(retry_statuses)
|
|
365
375
|
accept_statuses = set(accept_statuses)
|
|
366
376
|
assert not (common := sorted(retry_statuses & accept_statuses)), f"{common} is in both retry_statuses and accept_statuses!"
|
|
@@ -379,7 +389,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
379
389
|
**kwargs,
|
|
380
390
|
)
|
|
381
391
|
if prepared_request._asynchronous_auth:
|
|
382
|
-
# needs a solution
|
|
392
|
+
# needs a solution without internals coupling
|
|
383
393
|
r = await prepared_request._pending_async_auth(prepared_request)
|
|
384
394
|
prepared_request.__dict__.update(r.__dict__)
|
|
385
395
|
prepared_request.prepare_content_length(prepared_request.body)
|
|
@@ -390,17 +400,11 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
390
400
|
try:
|
|
391
401
|
await self.log_request(prepared_request)
|
|
392
402
|
response = await self.client.send(prepared_request, timeout=self.client.timeout, allow_redirects=follow_redirects, **settings)
|
|
393
|
-
if
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
json = response.json(parse_float=Decimal)
|
|
399
|
-
except Exception as exc:
|
|
400
|
-
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
401
|
-
else:
|
|
402
|
-
json = None
|
|
403
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
403
|
+
if response.status_code in retry_statuses:
|
|
404
|
+
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
405
|
+
elif response.status_code not in accept_statuses:
|
|
406
|
+
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
407
|
+
return await self.handle_process_response(response, expect_json=expect_json)
|
|
404
408
|
except (RequestError, RetryableError) as exc:
|
|
405
409
|
retry_failures.append(exc)
|
|
406
410
|
self.handle_retriable_failure(method, prepared_request.url, exc, retries_left, retry_budget)
|
|
@@ -416,6 +420,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
416
420
|
|
|
417
421
|
class HTTPService[CT = HTTPServiceContext]:
|
|
418
422
|
context_class: type[CT] = HTTPServiceContext
|
|
423
|
+
context_defaults: dict = {}
|
|
419
424
|
logger_name: str = "service"
|
|
420
425
|
logger: Logger
|
|
421
426
|
base_url: str
|
|
@@ -442,7 +447,7 @@ class HTTPService[CT = HTTPServiceContext]:
|
|
|
442
447
|
)
|
|
443
448
|
|
|
444
449
|
def context(self, **kwargs) -> CT:
|
|
445
|
-
return self.context_class(self, **kwargs)
|
|
450
|
+
return self.context_class(self, **self.context_defaults, **kwargs)
|
|
446
451
|
|
|
447
452
|
def auth(self, request: PreparedRequest, context: CT) -> PreparedRequest:
|
|
448
453
|
return request
|
|
@@ -452,7 +457,7 @@ class AsyncHTTPService[ACT = AsyncHTTPServiceContext](HTTPService[ACT]):
|
|
|
452
457
|
context_class: type[AsyncHTTPServiceContext] = AsyncHTTPServiceContext
|
|
453
458
|
|
|
454
459
|
def context(self, **kwargs) -> ACT:
|
|
455
|
-
return self.context_class(self, **kwargs)
|
|
460
|
+
return self.context_class(self, **self.context_defaults, **kwargs)
|
|
456
461
|
|
|
457
462
|
async def auth(self, request: PreparedRequest, context: ACT) -> PreparedRequest:
|
|
458
463
|
return request
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Clean Slate Utils - bunch of utility code, mostly Django/DRF specific.
|
|
5
5
|
Author-email: Ionel Cristian Mărieș <contact@ionelmc.ro>
|
|
6
6
|
License-Expression: BSD-2-Clause
|
|
@@ -69,9 +69,9 @@ Overview
|
|
|
69
69
|
.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/csu.svg
|
|
70
70
|
:alt: Supported implementations
|
|
71
71
|
:target: https://pypi.org/project/csu
|
|
72
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.
|
|
72
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-csu/v2.4.0.svg
|
|
73
73
|
:alt: Commits since latest release
|
|
74
|
-
:target: https://github.com/ionelmc/python-csu/compare/v2.
|
|
74
|
+
:target: https://github.com/ionelmc/python-csu/compare/v2.4.0...main
|
|
75
75
|
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/quality/g/ionelmc/python-csu/main.svg
|
|
76
76
|
:alt: Scrutinizer Status
|
|
77
77
|
:target: https://scrutinizer-ci.com/g/ionelmc/python-csu/
|
|
@@ -96,7 +96,9 @@ tests/cassettes/test_service/test_error.yaml
|
|
|
96
96
|
tests/cassettes/test_service/test_logging.yaml
|
|
97
97
|
tests/cassettes/test_service/test_redirects.yaml
|
|
98
98
|
tests/cassettes/test_service_auth/test_async.yaml
|
|
99
|
+
tests/cassettes/test_service_auth/test_async_auth.yaml
|
|
99
100
|
tests/cassettes/test_service_auth/test_sync.yaml
|
|
101
|
+
tests/cassettes/test_service_auth/test_sync_auth.yaml
|
|
100
102
|
tests/testproject/__init__.py
|
|
101
103
|
tests/testproject/models.py
|
|
102
104
|
tests/testproject/settings.py
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
body: null
|
|
4
|
+
headers:
|
|
5
|
+
Accept:
|
|
6
|
+
- '*/*'
|
|
7
|
+
Accept-Encoding:
|
|
8
|
+
- gzip, deflate, br
|
|
9
|
+
Authorization:
|
|
10
|
+
- Basic dXNyOnB3ZA==
|
|
11
|
+
Connection:
|
|
12
|
+
- keep-alive
|
|
13
|
+
User-Agent:
|
|
14
|
+
- niquests/3.18.6
|
|
15
|
+
method: GET
|
|
16
|
+
uri: http://127.0.0.1:37027/basic-auth/usr/pwd
|
|
17
|
+
response:
|
|
18
|
+
body:
|
|
19
|
+
string: "{\n \"authenticated\": true,\n \"user\": \"usr\"\n}\n"
|
|
20
|
+
headers:
|
|
21
|
+
Access-Control-Allow-Credentials:
|
|
22
|
+
- 'true'
|
|
23
|
+
Access-Control-Allow-Origin:
|
|
24
|
+
- '*'
|
|
25
|
+
Connection:
|
|
26
|
+
- Close
|
|
27
|
+
Content-Length:
|
|
28
|
+
- '45'
|
|
29
|
+
Content-Type:
|
|
30
|
+
- application/json
|
|
31
|
+
Date:
|
|
32
|
+
- Wed, 17 Jun 2026 16:14:27 GMT
|
|
33
|
+
Server:
|
|
34
|
+
- Pytest-HTTPBIN/0.1.0
|
|
35
|
+
status:
|
|
36
|
+
code: 200
|
|
37
|
+
message: OK
|
|
38
|
+
- request:
|
|
39
|
+
body: null
|
|
40
|
+
headers:
|
|
41
|
+
Accept:
|
|
42
|
+
- '*/*'
|
|
43
|
+
Accept-Encoding:
|
|
44
|
+
- gzip, deflate, br
|
|
45
|
+
Authorization:
|
|
46
|
+
- Basic dXNyOnB3ZA==
|
|
47
|
+
Connection:
|
|
48
|
+
- keep-alive
|
|
49
|
+
User-Agent:
|
|
50
|
+
- niquests/3.18.6
|
|
51
|
+
method: GET
|
|
52
|
+
uri: http://127.0.0.1:40371/basic-auth/usr/pwd
|
|
53
|
+
response:
|
|
54
|
+
body:
|
|
55
|
+
string: "{\n \"authenticated\": true,\n \"user\": \"usr\"\n}\n"
|
|
56
|
+
headers:
|
|
57
|
+
Access-Control-Allow-Credentials:
|
|
58
|
+
- 'true'
|
|
59
|
+
Access-Control-Allow-Origin:
|
|
60
|
+
- '*'
|
|
61
|
+
Connection:
|
|
62
|
+
- Close
|
|
63
|
+
Content-Length:
|
|
64
|
+
- '45'
|
|
65
|
+
Content-Type:
|
|
66
|
+
- application/json
|
|
67
|
+
Date:
|
|
68
|
+
- Wed, 17 Jun 2026 16:25:16 GMT
|
|
69
|
+
Server:
|
|
70
|
+
- Pytest-HTTPBIN/0.1.0
|
|
71
|
+
status:
|
|
72
|
+
code: 200
|
|
73
|
+
message: OK
|
|
74
|
+
- request:
|
|
75
|
+
body: null
|
|
76
|
+
headers:
|
|
77
|
+
Accept:
|
|
78
|
+
- '*/*'
|
|
79
|
+
Accept-Encoding:
|
|
80
|
+
- gzip, deflate, br
|
|
81
|
+
Authorization:
|
|
82
|
+
- Basic dXNyOnB3ZA==
|
|
83
|
+
Connection:
|
|
84
|
+
- keep-alive
|
|
85
|
+
User-Agent:
|
|
86
|
+
- niquests/3.18.6
|
|
87
|
+
method: GET
|
|
88
|
+
uri: http://127.0.0.1:38445/basic-auth/quite/bad
|
|
89
|
+
response:
|
|
90
|
+
body:
|
|
91
|
+
string: ''
|
|
92
|
+
headers:
|
|
93
|
+
Access-Control-Allow-Credentials:
|
|
94
|
+
- 'true'
|
|
95
|
+
Access-Control-Allow-Origin:
|
|
96
|
+
- '*'
|
|
97
|
+
Connection:
|
|
98
|
+
- Close
|
|
99
|
+
Content-Length:
|
|
100
|
+
- '0'
|
|
101
|
+
Date:
|
|
102
|
+
- Wed, 17 Jun 2026 16:25:39 GMT
|
|
103
|
+
Server:
|
|
104
|
+
- Pytest-HTTPBIN/0.1.0
|
|
105
|
+
WWW-Authenticate:
|
|
106
|
+
- Basic realm="Fake Realm"
|
|
107
|
+
status:
|
|
108
|
+
code: 401
|
|
109
|
+
message: Unauthorized
|
|
110
|
+
version: 1
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
body: null
|
|
4
|
+
headers:
|
|
5
|
+
Accept:
|
|
6
|
+
- '*/*'
|
|
7
|
+
Accept-Encoding:
|
|
8
|
+
- gzip, deflate, br
|
|
9
|
+
Authorization:
|
|
10
|
+
- Basic dXNyOnB3ZA==
|
|
11
|
+
Connection:
|
|
12
|
+
- keep-alive
|
|
13
|
+
User-Agent:
|
|
14
|
+
- niquests/3.18.6
|
|
15
|
+
method: GET
|
|
16
|
+
uri: http://127.0.0.1:37027/basic-auth/usr/pwd
|
|
17
|
+
response:
|
|
18
|
+
body:
|
|
19
|
+
string: "{\n \"authenticated\": true,\n \"user\": \"usr\"\n}\n"
|
|
20
|
+
headers:
|
|
21
|
+
Access-Control-Allow-Credentials:
|
|
22
|
+
- 'true'
|
|
23
|
+
Access-Control-Allow-Origin:
|
|
24
|
+
- '*'
|
|
25
|
+
Connection:
|
|
26
|
+
- Close
|
|
27
|
+
Content-Length:
|
|
28
|
+
- '45'
|
|
29
|
+
Content-Type:
|
|
30
|
+
- application/json
|
|
31
|
+
Date:
|
|
32
|
+
- Wed, 17 Jun 2026 16:14:27 GMT
|
|
33
|
+
Server:
|
|
34
|
+
- Pytest-HTTPBIN/0.1.0
|
|
35
|
+
status:
|
|
36
|
+
code: 200
|
|
37
|
+
message: OK
|
|
38
|
+
- request:
|
|
39
|
+
body: null
|
|
40
|
+
headers:
|
|
41
|
+
Accept:
|
|
42
|
+
- '*/*'
|
|
43
|
+
Accept-Encoding:
|
|
44
|
+
- gzip, deflate, br
|
|
45
|
+
Authorization:
|
|
46
|
+
- Basic dXNyOnB3ZA==
|
|
47
|
+
Connection:
|
|
48
|
+
- keep-alive
|
|
49
|
+
User-Agent:
|
|
50
|
+
- niquests/3.18.6
|
|
51
|
+
method: GET
|
|
52
|
+
uri: http://127.0.0.1:37027/basic-auth/quite/bad
|
|
53
|
+
response:
|
|
54
|
+
body:
|
|
55
|
+
string: ''
|
|
56
|
+
headers:
|
|
57
|
+
Access-Control-Allow-Credentials:
|
|
58
|
+
- 'true'
|
|
59
|
+
Access-Control-Allow-Origin:
|
|
60
|
+
- '*'
|
|
61
|
+
Connection:
|
|
62
|
+
- Close
|
|
63
|
+
Content-Length:
|
|
64
|
+
- '0'
|
|
65
|
+
Date:
|
|
66
|
+
- Wed, 17 Jun 2026 16:14:27 GMT
|
|
67
|
+
Server:
|
|
68
|
+
- Pytest-HTTPBIN/0.1.0
|
|
69
|
+
WWW-Authenticate:
|
|
70
|
+
- Basic realm="Fake Realm"
|
|
71
|
+
status:
|
|
72
|
+
code: 401
|
|
73
|
+
message: Unauthorized
|
|
74
|
+
- request:
|
|
75
|
+
body: null
|
|
76
|
+
headers:
|
|
77
|
+
Accept:
|
|
78
|
+
- '*/*'
|
|
79
|
+
Accept-Encoding:
|
|
80
|
+
- gzip, deflate, br
|
|
81
|
+
Authorization:
|
|
82
|
+
- Basic dXNyOnB3ZA==
|
|
83
|
+
Connection:
|
|
84
|
+
- keep-alive
|
|
85
|
+
User-Agent:
|
|
86
|
+
- niquests/3.18.6
|
|
87
|
+
method: GET
|
|
88
|
+
uri: http://127.0.0.1:40371/basic-auth/usr/pwd
|
|
89
|
+
response:
|
|
90
|
+
body:
|
|
91
|
+
string: "{\n \"authenticated\": true,\n \"user\": \"usr\"\n}\n"
|
|
92
|
+
headers:
|
|
93
|
+
Access-Control-Allow-Credentials:
|
|
94
|
+
- 'true'
|
|
95
|
+
Access-Control-Allow-Origin:
|
|
96
|
+
- '*'
|
|
97
|
+
Connection:
|
|
98
|
+
- Close
|
|
99
|
+
Content-Length:
|
|
100
|
+
- '45'
|
|
101
|
+
Content-Type:
|
|
102
|
+
- application/json
|
|
103
|
+
Date:
|
|
104
|
+
- Wed, 17 Jun 2026 16:25:16 GMT
|
|
105
|
+
Server:
|
|
106
|
+
- Pytest-HTTPBIN/0.1.0
|
|
107
|
+
status:
|
|
108
|
+
code: 200
|
|
109
|
+
message: OK
|
|
110
|
+
- request:
|
|
111
|
+
body: null
|
|
112
|
+
headers:
|
|
113
|
+
Accept:
|
|
114
|
+
- '*/*'
|
|
115
|
+
Accept-Encoding:
|
|
116
|
+
- gzip, deflate, br
|
|
117
|
+
Authorization:
|
|
118
|
+
- Basic dXNyOnB3ZA==
|
|
119
|
+
Connection:
|
|
120
|
+
- keep-alive
|
|
121
|
+
User-Agent:
|
|
122
|
+
- niquests/3.18.6
|
|
123
|
+
method: GET
|
|
124
|
+
uri: http://127.0.0.1:40371/basic-auth/quite/bad
|
|
125
|
+
response:
|
|
126
|
+
body:
|
|
127
|
+
string: ''
|
|
128
|
+
headers:
|
|
129
|
+
Access-Control-Allow-Credentials:
|
|
130
|
+
- 'true'
|
|
131
|
+
Access-Control-Allow-Origin:
|
|
132
|
+
- '*'
|
|
133
|
+
Connection:
|
|
134
|
+
- Close
|
|
135
|
+
Content-Length:
|
|
136
|
+
- '0'
|
|
137
|
+
Date:
|
|
138
|
+
- Wed, 17 Jun 2026 16:25:16 GMT
|
|
139
|
+
Server:
|
|
140
|
+
- Pytest-HTTPBIN/0.1.0
|
|
141
|
+
WWW-Authenticate:
|
|
142
|
+
- Basic realm="Fake Realm"
|
|
143
|
+
status:
|
|
144
|
+
code: 401
|
|
145
|
+
message: Unauthorized
|
|
146
|
+
version: 1
|
|
@@ -261,7 +261,7 @@ json.decoder.JSONDecodeError: Expecting value: line 2 column 5 (char 5)
|
|
|
261
261
|
During handling of the above exception, another exception occurred:
|
|
262
262
|
|
|
263
263
|
Traceback (most recent call last):
|
|
264
|
-
File "[&SERVICE_PATH]", line
|
|
264
|
+
File "[&SERVICE_PATH]", line 221, in handle_process_response
|
|
265
265
|
json = response.json(parse_float=Decimal)
|
|
266
266
|
File "[&MODELS_PATH]", line 1529, in json
|
|
267
267
|
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
|
|
@@ -276,7 +276,10 @@ Traceback (most recent call last):
|
|
|
276
276
|
File "[&TEST_PATH]", line 78, in json
|
|
277
277
|
return ctx.request("GET", "status/418", accept_statuses=[418]).json
|
|
278
278
|
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
279
|
-
File "[&SERVICE_PATH]", line
|
|
279
|
+
File "[&SERVICE_PATH]", line 302, in request
|
|
280
|
+
return self.handle_process_response(response, expect_json=expect_json)
|
|
281
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
282
|
+
File "[&SERVICE_PATH]", line 223, in handle_process_response
|
|
280
283
|
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
281
284
|
csu.exceptions.DecodeError: DecodeError(JSONDecodeError('Expecting value: line 2 column 5 (char 5)'), response=<Response Dummy [418]>)
|
|
282
285
|
(418, b'\\n -=[ teapot ]=-\\n\\n _...._\\n .\\' _ _ `.\\n | ."` ^ `". _,\\n \\\\_;`"---"`|//\\n | ;/\\n \\\\_ _/\\n `"""`\\n')
|
|
@@ -339,10 +342,7 @@ Traceback (most recent call last):
|
|
|
339
342
|
File "[&TEST_PATH]", line 47, in error
|
|
340
343
|
return ctx.request("GET", "status/599", expect_json=False)
|
|
341
344
|
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
342
|
-
File "[&SERVICE_PATH]", line
|
|
343
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
344
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
345
|
-
File "[&SERVICE_PATH]", line 229, in handle_process_response
|
|
345
|
+
File "[&SERVICE_PATH]", line 301, in request
|
|
346
346
|
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
347
347
|
csu.exceptions.UnexpectedStatusError: UnexpectedStatusError(<Response Dummy [599]>, accept_statuses=[200])
|
|
348
348
|
(599, b'')
|
|
@@ -350,7 +350,7 @@ csu.exceptions.UnexpectedStatusError: UnexpectedStatusError(<Response Dummy [599
|
|
|
350
350
|
The above exception was the direct cause of the following exception:
|
|
351
351
|
|
|
352
352
|
Traceback (most recent call last):
|
|
353
|
-
File "[&TEST_PATH]", line
|
|
353
|
+
File "[&TEST_PATH]", line 298, in test_logging
|
|
354
354
|
bingo.error()
|
|
355
355
|
~~~~~~~~~~~^^
|
|
356
356
|
File "[&TEST_PATH]", line 50, in error
|
|
@@ -542,10 +542,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
542
542
|
"""\
|
|
543
543
|
ERROR | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999%7D&expect_json=true&follow_redirects=false&retries_left=3&retry_count=0&retry_statuses=%7B200%7D !> RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"3","retry_count":"0","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=3&retry_count=0&retry_statuses={200}"}\\n', event_id=241212:01:000000.00) (will retry 3 more times)
|
|
544
544
|
Traceback (most recent call last):
|
|
545
|
-
File "[&SERVICE_PATH]", line
|
|
546
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
547
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
548
|
-
File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
545
|
+
File "[&SERVICE_PATH]", line 299, in request
|
|
549
546
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
550
547
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
551
548
|
(200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"3","retry_count":"0","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=3&retry_count=0&retry_statuses={200}"}\\n')
|
|
@@ -579,10 +576,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
579
576
|
"""\
|
|
580
577
|
ERROR | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999%7D&expect_json=true&follow_redirects=false&retries_left=2&retry_count=1&retry_statuses=%7B200%7D !> RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"2","retry_count":"1","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=2&retry_count=1&retry_statuses={200}"}\\n', event_id=241212:01:000000.00) (will retry 2 more times)
|
|
581
578
|
Traceback (most recent call last):
|
|
582
|
-
File "[&SERVICE_PATH]", line
|
|
583
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
584
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
585
|
-
File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
579
|
+
File "[&SERVICE_PATH]", line 299, in request
|
|
586
580
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
587
581
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
588
582
|
(200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"2","retry_count":"1","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=2&retry_count=1&retry_statuses={200}"}\\n')
|
|
@@ -616,10 +610,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
616
610
|
"""\
|
|
617
611
|
ERROR | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999%7D&expect_json=true&follow_redirects=false&retries_left=1&retry_count=2&retry_statuses=%7B200%7D !> RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"1","retry_count":"2","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=1&retry_count=2&retry_statuses={200}"}\\n', event_id=241212:01:000000.00) (will retry 1 more times)
|
|
618
612
|
Traceback (most recent call last):
|
|
619
|
-
File "[&SERVICE_PATH]", line
|
|
620
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
621
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
622
|
-
File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
613
|
+
File "[&SERVICE_PATH]", line 299, in request
|
|
623
614
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
624
615
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
625
616
|
(200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"1","retry_count":"2","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=1&retry_count=2&retry_statuses={200}"}\\n')
|
|
@@ -667,43 +658,31 @@ ERROR | (renderer: NoneType)
|
|
|
667
658
|
| ...<6 lines>...
|
|
668
659
|
| accept_statuses=[999],
|
|
669
660
|
| )
|
|
670
|
-
| File "[&SERVICE_PATH]", line
|
|
661
|
+
| File "[&SERVICE_PATH]", line 313, in request
|
|
671
662
|
| raise ExhaustedRetriesError(retry_budget, retry_failures, event_id=self.event_id)
|
|
672
663
|
| csu.exceptions.ExhaustedRetriesError: ExhaustedRetriesError(3)
|
|
673
664
|
| ([RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"3","retry_count":"0","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=3&retry_count=0&retry_statuses={200}"}\\n', event_id=241212:01:000000.00), RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"2","retry_count":"1","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=2&retry_count=1&retry_statuses={200}"}\\n', event_id=241212:01:000000.00), RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"1","retry_count":"2","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=1&retry_count=2&retry_statuses={200}"}\\n', event_id=241212:01:000000.00), RetryableStatusError('200 not in [999] but in [200]', 200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"0","retry_count":"3","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=0&retry_count=3&retry_statuses={200}"}\\n', event_id=241212:01:000000.00)],)
|
|
674
665
|
+-+---------------- 1 ----------------
|
|
675
666
|
| Traceback (most recent call last):
|
|
676
|
-
| File "[&SERVICE_PATH]", line
|
|
677
|
-
| return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
678
|
-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
679
|
-
| File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
667
|
+
| File "[&SERVICE_PATH]", line 299, in request
|
|
680
668
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
681
669
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
682
670
|
| (200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"3","retry_count":"0","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=3&retry_count=0&retry_statuses={200}"}\\n')
|
|
683
671
|
+---------------- 2 ----------------
|
|
684
672
|
| Traceback (most recent call last):
|
|
685
|
-
| File "[&SERVICE_PATH]", line
|
|
686
|
-
| return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
687
|
-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
688
|
-
| File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
673
|
+
| File "[&SERVICE_PATH]", line 299, in request
|
|
689
674
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
690
675
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
691
676
|
| (200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"2","retry_count":"1","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=2&retry_count=1&retry_statuses={200}"}\\n')
|
|
692
677
|
+---------------- 3 ----------------
|
|
693
678
|
| Traceback (most recent call last):
|
|
694
|
-
| File "[&SERVICE_PATH]", line
|
|
695
|
-
| return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
696
|
-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
697
|
-
| File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
679
|
+
| File "[&SERVICE_PATH]", line 299, in request
|
|
698
680
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
699
681
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
700
682
|
| (200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"1","retry_count":"2","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=1&retry_count=2&retry_statuses={200}"}\\n')
|
|
701
683
|
+---------------- 4 ----------------
|
|
702
684
|
| Traceback (most recent call last):
|
|
703
|
-
| File "[&SERVICE_PATH]", line
|
|
704
|
-
| return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
705
|
-
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
706
|
-
| File "[&SERVICE_PATH]", line 227, in handle_process_response
|
|
685
|
+
| File "[&SERVICE_PATH]", line 299, in request
|
|
707
686
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
708
687
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
709
688
|
| (200, b'{"args":{"accept_statuses":"{999}","expect_json":"true","follow_redirects":"false","foo":"bar","retries_left":"0","retry_count":"3","retry_statuses":"{200}"},"data":"config={\\'custom\\': \\'config\\'}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"27","Host":"127.0.0.1:12345","User-Agent":"python-httpx/0.X.X"},"json":null,"method":"POST","origin":"127.0.0.1","url":"http://127.0.0.1:12345/anything?foo=bar&accept_statuses={999}&expect_json=true&follow_redirects=false&retries_left=0&retry_count=3&retry_statuses={200}"}\\n')
|
|
@@ -3,7 +3,9 @@ from os import getpid
|
|
|
3
3
|
import pytest
|
|
4
4
|
from inline_snapshot import snapshot
|
|
5
5
|
from niquests import PreparedRequest
|
|
6
|
+
from niquests.auth import HTTPBasicAuth
|
|
6
7
|
|
|
8
|
+
from csu.exceptions import UnexpectedStatusError
|
|
7
9
|
from csu.service import AsyncHTTPService
|
|
8
10
|
from csu.service import HTTPService
|
|
9
11
|
from csu.service import HTTPServiceContext
|
|
@@ -41,6 +43,106 @@ class AsyncAuthedService(AsyncHTTPService):
|
|
|
41
43
|
await ctx.request("GET", "json")
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
class VanillaAuthedService(HTTPService):
|
|
47
|
+
def __init__(self, url, user, password):
|
|
48
|
+
super().__init__(config={"BASE_URL": url})
|
|
49
|
+
self.context_defaults = {"auth": HTTPBasicAuth(user, password)}
|
|
50
|
+
|
|
51
|
+
def test(self, path):
|
|
52
|
+
with self.context() as ctx:
|
|
53
|
+
ctx.request("GET", path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class VanillaAsyncAuthedService(AsyncHTTPService):
|
|
57
|
+
def __init__(self, url, user, password):
|
|
58
|
+
super().__init__(config={"BASE_URL": url})
|
|
59
|
+
self.context_defaults = {"auth": HTTPBasicAuth(user, password)}
|
|
60
|
+
|
|
61
|
+
async def test(self, path):
|
|
62
|
+
async with self.context() as ctx:
|
|
63
|
+
await ctx.request("GET", path)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.vcr
|
|
67
|
+
def test_sync_auth(httpbin, fake_accident_id, caplogs):
|
|
68
|
+
srv = VanillaAuthedService(httpbin.url, "usr", "pwd")
|
|
69
|
+
srv.test("basic-auth/usr/pwd")
|
|
70
|
+
assert caplogs.get(
|
|
71
|
+
HTTPBIN=f"{httpbin.host}:{httpbin.port}",
|
|
72
|
+
PID=f"[pid:{getpid()}]",
|
|
73
|
+
) == snapshot(
|
|
74
|
+
[
|
|
75
|
+
"INFO | [&PID] Setting up VanillaAuthedService with BASE_URL='http://[&HTTPBIN]' TIMEOUT=60 RETRIES=3 VERIFY=True",
|
|
76
|
+
"""\
|
|
77
|
+
INFO | [01:000000.00] GET http://[&HTTPBIN]/basic-auth/usr/pwd
|
|
78
|
+
------------------------------------------------------------- request headers --------------------------------------------------------------
|
|
79
|
+
Accept: '*/*'
|
|
80
|
+
Accept-Encoding: 'gzip, deflate, br'
|
|
81
|
+
Authorization: 'Basic dXNyOnB3ZA=='
|
|
82
|
+
Connection: 'keep-alive'
|
|
83
|
+
User-Agent: 'niquests/3.18.6'\
|
|
84
|
+
""",
|
|
85
|
+
"""\
|
|
86
|
+
INFO | [01:000000.00] GET http://[&HTTPBIN]/basic-auth/usr/pwd => 200 in 0.0000s (HNone)
|
|
87
|
+
------------------------------------------------------------- response headers -------------------------------------------------------------
|
|
88
|
+
Access-Control-Allow-Credentials: 'true'
|
|
89
|
+
Access-Control-Allow-Origin: '*'
|
|
90
|
+
Connection: 'Close'
|
|
91
|
+
Content-Length: '45'
|
|
92
|
+
Content-Type: 'application/json'
|
|
93
|
+
Date: 'Wed, 17 Jun 2026 16:14:27 GMT'
|
|
94
|
+
Server: 'Pytest-HTTPBIN/0.1.0'
|
|
95
|
+
---------------------------------------------- response body ('application/json' - 45 bytes) -----------------------------------------------
|
|
96
|
+
b'{\\n "authenticated": true,\\n "user": "usr"\\n}\\n'
|
|
97
|
+
============================================================================================================================================\
|
|
98
|
+
""",
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
with pytest.raises(UnexpectedStatusError):
|
|
102
|
+
srv.test("basic-auth/quite/bad")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.vcr
|
|
106
|
+
@pytest.mark.asyncio
|
|
107
|
+
async def test_async_auth(httpbin, fake_accident_id, caplogs):
|
|
108
|
+
srv = VanillaAsyncAuthedService(httpbin.url, "usr", "pwd")
|
|
109
|
+
await srv.test("basic-auth/usr/pwd")
|
|
110
|
+
assert caplogs.get(
|
|
111
|
+
HTTPBIN=f"{httpbin.host}:{httpbin.port}",
|
|
112
|
+
PID=f"[pid:{getpid()}]",
|
|
113
|
+
) == snapshot(
|
|
114
|
+
[
|
|
115
|
+
"INFO | [&PID] Setting up VanillaAsyncAuthedService with BASE_URL='http://[&HTTPBIN]' TIMEOUT=60 RETRIES=3 VERIFY=True",
|
|
116
|
+
"""\
|
|
117
|
+
INFO | [01:000000.00] GET http://[&HTTPBIN]/basic-auth/usr/pwd
|
|
118
|
+
------------------------------------------------------------- request headers --------------------------------------------------------------
|
|
119
|
+
Accept: '*/*'
|
|
120
|
+
Accept-Encoding: 'gzip, deflate, br'
|
|
121
|
+
Authorization: 'Basic dXNyOnB3ZA=='
|
|
122
|
+
Connection: 'keep-alive'
|
|
123
|
+
User-Agent: 'niquests/3.18.6'\
|
|
124
|
+
""",
|
|
125
|
+
"INFO | Playing response for <Request (GET) http://[&HTTPBIN]/basic-auth/usr/pwd> from cassette",
|
|
126
|
+
"""\
|
|
127
|
+
INFO | [01:000000.00] GET http://[&HTTPBIN]/basic-auth/usr/pwd => 200 in 0.0000s (H0)
|
|
128
|
+
------------------------------------------------------------- response headers -------------------------------------------------------------
|
|
129
|
+
Access-Control-Allow-Credentials: 'true'
|
|
130
|
+
Access-Control-Allow-Origin: '*'
|
|
131
|
+
Connection: 'Close'
|
|
132
|
+
Content-Length: '45'
|
|
133
|
+
Content-Type: 'application/json'
|
|
134
|
+
Date: 'Wed, 17 Jun 2026 16:14:27 GMT'
|
|
135
|
+
Server: 'Pytest-HTTPBIN/0.1.0'
|
|
136
|
+
---------------------------------------------- response body ('application/json' - 45 bytes) -----------------------------------------------
|
|
137
|
+
b'{\\n "authenticated": true,\\n "user": "usr"\\n}\\n'
|
|
138
|
+
============================================================================================================================================\
|
|
139
|
+
""",
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
with pytest.raises(UnexpectedStatusError):
|
|
143
|
+
await srv.test("basic-auth/quite/bad")
|
|
144
|
+
|
|
145
|
+
|
|
44
146
|
@pytest.mark.vcr
|
|
45
147
|
def test_sync(httpbin, fake_accident_id, caplogs):
|
|
46
148
|
srv = AuthedService(httpbin.url)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csu-2.3.6 → csu-2.4.0}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csu-2.3.6 → csu-2.4.0}/tox.ini
RENAMED
|
File without changes
|