csu 2.3.6__tar.gz → 2.4.1__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.1}/.bumpversion.cfg +1 -1
- {csu-2.3.6 → csu-2.4.1}/.cookiecutterrc +1 -1
- {csu-2.3.6 → csu-2.4.1}/PKG-INFO +3 -3
- {csu-2.3.6 → csu-2.4.1}/README.rst +2 -2
- {csu-2.3.6 → csu-2.4.1}/pyproject.toml +1 -1
- {csu-2.3.6 → csu-2.4.1}/src/csu/__init__.py +1 -1
- {csu-2.3.6 → csu-2.4.1}/src/csu/service.py +47 -40
- {csu-2.3.6 → csu-2.4.1}/src/csu.egg-info/PKG-INFO +3 -3
- {csu-2.3.6 → csu-2.4.1}/src/csu.egg-info/SOURCES.txt +2 -0
- csu-2.4.1/tests/cassettes/test_service_auth/test_async_auth.yaml +110 -0
- csu-2.4.1/tests/cassettes/test_service_auth/test_sync_auth.yaml +146 -0
- {csu-2.3.6 → csu-2.4.1}/tests/test_service.py +18 -41
- {csu-2.3.6 → csu-2.4.1}/tests/test_service_auth.py +102 -0
- {csu-2.3.6 → csu-2.4.1}/.coveragerc +0 -0
- {csu-2.3.6 → csu-2.4.1}/.editorconfig +0 -0
- {csu-2.3.6 → csu-2.4.1}/.github/workflows/github-actions.yml +0 -0
- {csu-2.3.6 → csu-2.4.1}/.pre-commit-config.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/.tombi.toml +0 -0
- {csu-2.3.6 → csu-2.4.1}/AUTHORS.rst +0 -0
- {csu-2.3.6 → csu-2.4.1}/CHANGELOG.rst +0 -0
- {csu-2.3.6 → csu-2.4.1}/CONTRIBUTING.rst +0 -0
- {csu-2.3.6 → csu-2.4.1}/LICENSE +0 -0
- {csu-2.3.6 → csu-2.4.1}/MANIFEST.in +0 -0
- {csu-2.3.6 → csu-2.4.1}/ci/bootstrap.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/ci/requirements.txt +0 -0
- {csu-2.3.6 → csu-2.4.1}/ci/templates/.github/workflows/github-actions.yml +0 -0
- {csu-2.3.6 → csu-2.4.1}/pytest.ini +0 -0
- {csu-2.3.6 → csu-2.4.1}/setup.cfg +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/admin.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/conf.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/consts.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/auth.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/fields.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/forms.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/perms.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/phonenumber.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/serializers.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/test_auth.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/test_fields.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/test_forms.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/test_phonenumber.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/test_serializers.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/drf/views.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/enums.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/env.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/exceptions.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/export.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/fixups.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/forms/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/forms/crispy.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/forms/fields.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/gettext.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/gettext_lazy.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/logging.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/management.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/models.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/query.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/routers.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/templates/api_exception.html +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_consts.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_env.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_exceptions.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_logging.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_management.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_service.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_timezones.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_utils.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/test_xml.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/timezones.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/utils.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/views.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/admin.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/asgi.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/engine.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/enums.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/job.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/models.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/worker/registry.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/wsgi.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu/xml.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu.egg-info/dependency_links.txt +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu.egg-info/requires.txt +0 -0
- {csu-2.3.6 → csu-2.4.1}/src/csu.egg-info/top_level.txt +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_decoding.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_error.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_logging.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service/test_redirects.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/conftest.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/exampleworker.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/test_exampleworker.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/test_models.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/test_views.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/test_worker.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/testproject/__init__.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/testproject/fixtures/testuser.json +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/testproject/models.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/testproject/settings.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tests/testproject/urls.py +0 -0
- {csu-2.3.6 → csu-2.4.1}/tox.ini +0 -0
{csu-2.3.6 → csu-2.4.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
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.1.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.1...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.1.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.1...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/
|
|
@@ -16,9 +16,9 @@ from niquests import Response
|
|
|
16
16
|
from niquests import Session
|
|
17
17
|
from niquests.auth import AsyncAuthBase
|
|
18
18
|
from niquests.auth import AuthBase
|
|
19
|
+
from niquests.exceptions import ConnectionError
|
|
19
20
|
from niquests.typing import TLSClientCertType
|
|
20
21
|
from niquests.typing import TLSVerifyType
|
|
21
|
-
from urllib3_future.exceptions import RequestError
|
|
22
22
|
|
|
23
23
|
from .conf import SERVICE_REQUEST_CONTENT_LOG_LIMIT
|
|
24
24
|
from .conf import SERVICE_REQUEST_SHOW_HEADERS
|
|
@@ -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,
|
|
@@ -270,6 +265,7 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
270
265
|
expect_json: bool = True,
|
|
271
266
|
follow_redirects: bool = False,
|
|
272
267
|
retry_statuses: Iterable[int] = (500, 502, 503, 504),
|
|
268
|
+
retry_exceptions: Iterable[type[Exception]] = (ConnectionError, RetryableError),
|
|
273
269
|
retry: bool = True,
|
|
274
270
|
proxy: str | None = None,
|
|
275
271
|
stream: bool | None = None,
|
|
@@ -300,15 +296,12 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
300
296
|
try:
|
|
301
297
|
self.log_request(prepared_request)
|
|
302
298
|
response = self.client.send(prepared_request, timeout=self.client.timeout, allow_redirects=follow_redirects, **settings)
|
|
303
|
-
if
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
json = None
|
|
310
|
-
return self.handle_process_response(response, retry_statuses=retry_statuses, accept_statuses=accept_statuses, json=json)
|
|
311
|
-
except (RequestError, RetryableError) as exc:
|
|
299
|
+
if response.status_code in retry_statuses:
|
|
300
|
+
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
301
|
+
elif response.status_code not in accept_statuses:
|
|
302
|
+
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
303
|
+
return self.handle_process_response(response, expect_json=expect_json)
|
|
304
|
+
except retry_exceptions as exc:
|
|
312
305
|
retry_failures.append(exc)
|
|
313
306
|
self.handle_retriable_failure(method, prepared_request.url, exc, retries_left, retry_budget)
|
|
314
307
|
except (InternalServiceError, OpenServiceError) as exc:
|
|
@@ -321,7 +314,7 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
|
|
|
321
314
|
raise ExhaustedRetriesError(retry_budget, retry_failures, event_id=self.event_id)
|
|
322
315
|
|
|
323
316
|
|
|
324
|
-
class AsyncHTTPServiceContext[
|
|
317
|
+
class AsyncHTTPServiceContext[RTYPE = HTTPServiceResponse](HTTPServiceContext[RTYPE]):
|
|
325
318
|
auth_class = AsyncHTTPServiceAuth
|
|
326
319
|
client_class = AsyncSession
|
|
327
320
|
client: AsyncSession
|
|
@@ -345,6 +338,24 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
345
338
|
await self.client.__aexit__(exc_type, exc_val, exc_tb)
|
|
346
339
|
self.handle_exit(exc_type, exc_val, exc_tb)
|
|
347
340
|
|
|
341
|
+
async def handle_process_response(self, response: Response, *, expect_json: bool) -> RTYPE:
|
|
342
|
+
if expect_json:
|
|
343
|
+
try:
|
|
344
|
+
if isinstance(response, AsyncResponse):
|
|
345
|
+
json = await response.json(parse_float=Decimal)
|
|
346
|
+
else:
|
|
347
|
+
json = response.json(parse_float=Decimal)
|
|
348
|
+
except Exception as exc:
|
|
349
|
+
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
350
|
+
else:
|
|
351
|
+
json = None
|
|
352
|
+
return HTTPServiceResponse(
|
|
353
|
+
status=response.status_code,
|
|
354
|
+
http_response=response,
|
|
355
|
+
json=json,
|
|
356
|
+
event_id=self.event_id,
|
|
357
|
+
)
|
|
358
|
+
|
|
348
359
|
async def request(
|
|
349
360
|
self,
|
|
350
361
|
method,
|
|
@@ -354,13 +365,14 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
354
365
|
expect_json: bool = True,
|
|
355
366
|
follow_redirects: bool = False,
|
|
356
367
|
retry_statuses: Iterable[int] = (500, 502, 503, 504),
|
|
368
|
+
retry_exceptions: Iterable[type[Exception]] = (ConnectionError, RetryableError),
|
|
357
369
|
retry: bool = True,
|
|
358
370
|
proxy: str | None = None,
|
|
359
371
|
stream: bool | None = None,
|
|
360
372
|
verify: TLSVerifyType | None = None,
|
|
361
373
|
cert: TLSClientCertType | None = None,
|
|
362
374
|
**kwargs,
|
|
363
|
-
) ->
|
|
375
|
+
) -> RTYPE:
|
|
364
376
|
retry_statuses = set(retry_statuses)
|
|
365
377
|
accept_statuses = set(accept_statuses)
|
|
366
378
|
assert not (common := sorted(retry_statuses & accept_statuses)), f"{common} is in both retry_statuses and accept_statuses!"
|
|
@@ -379,7 +391,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
379
391
|
**kwargs,
|
|
380
392
|
)
|
|
381
393
|
if prepared_request._asynchronous_auth:
|
|
382
|
-
# needs a solution
|
|
394
|
+
# needs a solution without internals coupling
|
|
383
395
|
r = await prepared_request._pending_async_auth(prepared_request)
|
|
384
396
|
prepared_request.__dict__.update(r.__dict__)
|
|
385
397
|
prepared_request.prepare_content_length(prepared_request.body)
|
|
@@ -390,18 +402,12 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
390
402
|
try:
|
|
391
403
|
await self.log_request(prepared_request)
|
|
392
404
|
response = await self.client.send(prepared_request, timeout=self.client.timeout, allow_redirects=follow_redirects, **settings)
|
|
393
|
-
if
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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)
|
|
404
|
-
except (RequestError, RetryableError) as exc:
|
|
405
|
+
if response.status_code in retry_statuses:
|
|
406
|
+
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
407
|
+
elif response.status_code not in accept_statuses:
|
|
408
|
+
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
409
|
+
return await self.handle_process_response(response, expect_json=expect_json)
|
|
410
|
+
except retry_exceptions as exc:
|
|
405
411
|
retry_failures.append(exc)
|
|
406
412
|
self.handle_retriable_failure(method, prepared_request.url, exc, retries_left, retry_budget)
|
|
407
413
|
except (InternalServiceError, OpenServiceError) as exc:
|
|
@@ -416,6 +422,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
|
|
|
416
422
|
|
|
417
423
|
class HTTPService[CT = HTTPServiceContext]:
|
|
418
424
|
context_class: type[CT] = HTTPServiceContext
|
|
425
|
+
context_defaults: dict = {}
|
|
419
426
|
logger_name: str = "service"
|
|
420
427
|
logger: Logger
|
|
421
428
|
base_url: str
|
|
@@ -442,7 +449,7 @@ class HTTPService[CT = HTTPServiceContext]:
|
|
|
442
449
|
)
|
|
443
450
|
|
|
444
451
|
def context(self, **kwargs) -> CT:
|
|
445
|
-
return self.context_class(self, **kwargs)
|
|
452
|
+
return self.context_class(self, **self.context_defaults, **kwargs)
|
|
446
453
|
|
|
447
454
|
def auth(self, request: PreparedRequest, context: CT) -> PreparedRequest:
|
|
448
455
|
return request
|
|
@@ -452,7 +459,7 @@ class AsyncHTTPService[ACT = AsyncHTTPServiceContext](HTTPService[ACT]):
|
|
|
452
459
|
context_class: type[AsyncHTTPServiceContext] = AsyncHTTPServiceContext
|
|
453
460
|
|
|
454
461
|
def context(self, **kwargs) -> ACT:
|
|
455
|
-
return self.context_class(self, **kwargs)
|
|
462
|
+
return self.context_class(self, **self.context_defaults, **kwargs)
|
|
456
463
|
|
|
457
464
|
async def auth(self, request: PreparedRequest, context: ACT) -> PreparedRequest:
|
|
458
465
|
return request
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csu
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
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.1.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.1...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
|
|
@@ -154,7 +154,6 @@ def test_error(fake_accident_id, caplogs, httpbin):
|
|
|
154
154
|
bingo.error()
|
|
155
155
|
assert exc.value.message == "It's ok."
|
|
156
156
|
api_exc = exc.value.as_api_service_error()
|
|
157
|
-
exc.value.details[0]
|
|
158
157
|
assert api_exc.status_code == 200
|
|
159
158
|
assert api_exc.detail == {"accident_id": "241212:01:000000.00", "code": "ok", "detail": "It's ok."}
|
|
160
159
|
|
|
@@ -261,7 +260,7 @@ json.decoder.JSONDecodeError: Expecting value: line 2 column 5 (char 5)
|
|
|
261
260
|
During handling of the above exception, another exception occurred:
|
|
262
261
|
|
|
263
262
|
Traceback (most recent call last):
|
|
264
|
-
File "[&SERVICE_PATH]", line
|
|
263
|
+
File "[&SERVICE_PATH]", line 221, in handle_process_response
|
|
265
264
|
json = response.json(parse_float=Decimal)
|
|
266
265
|
File "[&MODELS_PATH]", line 1529, in json
|
|
267
266
|
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
|
|
@@ -270,13 +269,16 @@ niquests.exceptions.JSONDecodeError: Expecting value: line 2 column 5 (char 5)
|
|
|
270
269
|
The above exception was the direct cause of the following exception:
|
|
271
270
|
|
|
272
271
|
Traceback (most recent call last):
|
|
273
|
-
File "[&TEST_PATH]", line
|
|
272
|
+
File "[&TEST_PATH]", line 202, in test_decoding
|
|
274
273
|
bingo.json()
|
|
275
274
|
~~~~~~~~~~^^
|
|
276
275
|
File "[&TEST_PATH]", line 78, in json
|
|
277
276
|
return ctx.request("GET", "status/418", accept_statuses=[418]).json
|
|
278
277
|
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
279
|
-
File "[&SERVICE_PATH]", line
|
|
278
|
+
File "[&SERVICE_PATH]", line 303, in request
|
|
279
|
+
return self.handle_process_response(response, expect_json=expect_json)
|
|
280
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
281
|
+
File "[&SERVICE_PATH]", line 223, in handle_process_response
|
|
280
282
|
raise DecodeError(response, exc, event_id=self.event_id) from exc
|
|
281
283
|
csu.exceptions.DecodeError: DecodeError(JSONDecodeError('Expecting value: line 2 column 5 (char 5)'), response=<Response Dummy [418]>)
|
|
282
284
|
(418, b'\\n -=[ teapot ]=-\\n\\n _...._\\n .\\' _ _ `.\\n | ."` ^ `". _,\\n \\\\_;`"---"`|//\\n | ;/\\n \\\\_ _/\\n `"""`\\n')
|
|
@@ -293,7 +295,6 @@ def test_logging(fake_accident_id, caplogs, httpbin):
|
|
|
293
295
|
bingo = BingoService(httpbin.url)
|
|
294
296
|
with pytest.raises(OkError) as exc:
|
|
295
297
|
bingo.error()
|
|
296
|
-
exc.value.details[0]
|
|
297
298
|
exception_handler(exc.value, {})
|
|
298
299
|
assert len(fake_accident_id.calls) == 1
|
|
299
300
|
assert fake_accident_id.calls[0].startswith("service.py:")
|
|
@@ -339,10 +340,7 @@ Traceback (most recent call last):
|
|
|
339
340
|
File "[&TEST_PATH]", line 47, in error
|
|
340
341
|
return ctx.request("GET", "status/599", expect_json=False)
|
|
341
342
|
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
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
|
|
343
|
+
File "[&SERVICE_PATH]", line 302, in request
|
|
346
344
|
raise UnexpectedStatusError(response, accept_statuses, event_id=self.event_id)
|
|
347
345
|
csu.exceptions.UnexpectedStatusError: UnexpectedStatusError(<Response Dummy [599]>, accept_statuses=[200])
|
|
348
346
|
(599, b'')
|
|
@@ -350,7 +348,7 @@ csu.exceptions.UnexpectedStatusError: UnexpectedStatusError(<Response Dummy [599
|
|
|
350
348
|
The above exception was the direct cause of the following exception:
|
|
351
349
|
|
|
352
350
|
Traceback (most recent call last):
|
|
353
|
-
File "[&TEST_PATH]", line
|
|
351
|
+
File "[&TEST_PATH]", line 297, in test_logging
|
|
354
352
|
bingo.error()
|
|
355
353
|
~~~~~~~~~~~^^
|
|
356
354
|
File "[&TEST_PATH]", line 50, in error
|
|
@@ -542,10 +540,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
542
540
|
"""\
|
|
543
541
|
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
542
|
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
|
|
543
|
+
File "[&SERVICE_PATH]", line 300, in request
|
|
549
544
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
550
545
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
551
546
|
(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 +574,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
579
574
|
"""\
|
|
580
575
|
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
576
|
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
|
|
577
|
+
File "[&SERVICE_PATH]", line 300, in request
|
|
586
578
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
587
579
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
588
580
|
(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 +608,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
|
|
|
616
608
|
"""\
|
|
617
609
|
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
610
|
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
|
|
611
|
+
File "[&SERVICE_PATH]", line 300, in request
|
|
623
612
|
raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
624
613
|
csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
625
614
|
(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')
|
|
@@ -658,52 +647,40 @@ ERROR | (renderer: NoneType)
|
|
|
658
647
|
{}
|
|
659
648
|
---------------------------------------------------------------- traceback -----------------------------------------------------------------
|
|
660
649
|
+ Exception Group Traceback (most recent call last):
|
|
661
|
-
| File "[&TEST_PATH]", line
|
|
650
|
+
| File "[&TEST_PATH]", line 435, in test_custom_context
|
|
662
651
|
| custom.run()
|
|
663
652
|
| ~~~~~~~~~~^^
|
|
664
|
-
| File "[&TEST_PATH]", line
|
|
653
|
+
| File "[&TEST_PATH]", line 418, in run
|
|
665
654
|
| resp = ctx.request(
|
|
666
655
|
| "POST",
|
|
667
656
|
| ...<6 lines>...
|
|
668
657
|
| accept_statuses=[999],
|
|
669
658
|
| )
|
|
670
|
-
| File "[&SERVICE_PATH]", line
|
|
659
|
+
| File "[&SERVICE_PATH]", line 314, in request
|
|
671
660
|
| raise ExhaustedRetriesError(retry_budget, retry_failures, event_id=self.event_id)
|
|
672
661
|
| csu.exceptions.ExhaustedRetriesError: ExhaustedRetriesError(3)
|
|
673
662
|
| ([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
663
|
+-+---------------- 1 ----------------
|
|
675
664
|
| 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
|
|
665
|
+
| File "[&SERVICE_PATH]", line 300, in request
|
|
680
666
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
681
667
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
682
668
|
| (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
669
|
+---------------- 2 ----------------
|
|
684
670
|
| 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
|
|
671
|
+
| File "[&SERVICE_PATH]", line 300, in request
|
|
689
672
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
690
673
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
691
674
|
| (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
675
|
+---------------- 3 ----------------
|
|
693
676
|
| 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
|
|
677
|
+
| File "[&SERVICE_PATH]", line 300, in request
|
|
698
678
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
699
679
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
700
680
|
| (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
681
|
+---------------- 4 ----------------
|
|
702
682
|
| 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
|
|
683
|
+
| File "[&SERVICE_PATH]", line 300, in request
|
|
707
684
|
| raise RetryableStatusError(response, accept_statuses, retry_statuses, event_id=self.event_id)
|
|
708
685
|
| csu.exceptions.RetryableStatusError: RetryableStatusError(<Response Dummy [200]>, accept_statuses=[999], retry_statuses=[200])
|
|
709
686
|
| (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.1}/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.1}/tox.ini
RENAMED
|
File without changes
|