csu 2.3.5__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.
Files changed (109) hide show
  1. {csu-2.3.5 → csu-2.4.0}/.bumpversion.cfg +1 -1
  2. {csu-2.3.5 → csu-2.4.0}/.cookiecutterrc +1 -1
  3. {csu-2.3.5 → csu-2.4.0}/.pre-commit-config.yaml +2 -2
  4. {csu-2.3.5 → csu-2.4.0}/PKG-INFO +3 -3
  5. {csu-2.3.5 → csu-2.4.0}/README.rst +2 -2
  6. {csu-2.3.5 → csu-2.4.0}/pyproject.toml +1 -1
  7. {csu-2.3.5 → csu-2.4.0}/src/csu/__init__.py +1 -1
  8. {csu-2.3.5 → csu-2.4.0}/src/csu/service.py +44 -39
  9. {csu-2.3.5 → csu-2.4.0}/src/csu.egg-info/PKG-INFO +3 -3
  10. {csu-2.3.5 → csu-2.4.0}/src/csu.egg-info/SOURCES.txt +2 -0
  11. csu-2.4.0/tests/cassettes/test_service_auth/test_async_auth.yaml +110 -0
  12. csu-2.4.0/tests/cassettes/test_service_auth/test_sync_auth.yaml +146 -0
  13. csu-2.4.0/tests/conftest.py +186 -0
  14. {csu-2.3.5 → csu-2.4.0}/tests/test_service.py +15 -36
  15. {csu-2.3.5 → csu-2.4.0}/tests/test_service_auth.py +114 -10
  16. csu-2.3.5/tests/conftest.py +0 -56
  17. {csu-2.3.5 → csu-2.4.0}/.coveragerc +0 -0
  18. {csu-2.3.5 → csu-2.4.0}/.editorconfig +0 -0
  19. {csu-2.3.5 → csu-2.4.0}/.github/workflows/github-actions.yml +0 -0
  20. {csu-2.3.5 → csu-2.4.0}/.tombi.toml +0 -0
  21. {csu-2.3.5 → csu-2.4.0}/AUTHORS.rst +0 -0
  22. {csu-2.3.5 → csu-2.4.0}/CHANGELOG.rst +0 -0
  23. {csu-2.3.5 → csu-2.4.0}/CONTRIBUTING.rst +0 -0
  24. {csu-2.3.5 → csu-2.4.0}/LICENSE +0 -0
  25. {csu-2.3.5 → csu-2.4.0}/MANIFEST.in +0 -0
  26. {csu-2.3.5 → csu-2.4.0}/ci/bootstrap.py +0 -0
  27. {csu-2.3.5 → csu-2.4.0}/ci/requirements.txt +0 -0
  28. {csu-2.3.5 → csu-2.4.0}/ci/templates/.github/workflows/github-actions.yml +0 -0
  29. {csu-2.3.5 → csu-2.4.0}/pytest.ini +0 -0
  30. {csu-2.3.5 → csu-2.4.0}/setup.cfg +0 -0
  31. {csu-2.3.5 → csu-2.4.0}/src/csu/admin.py +0 -0
  32. {csu-2.3.5 → csu-2.4.0}/src/csu/conf.py +0 -0
  33. {csu-2.3.5 → csu-2.4.0}/src/csu/consts.py +0 -0
  34. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/__init__.py +0 -0
  35. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/auth.py +0 -0
  36. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/fields.py +0 -0
  37. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/forms.py +0 -0
  38. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/perms.py +0 -0
  39. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/phonenumber.py +0 -0
  40. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/serializers.py +0 -0
  41. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/test_auth.py +0 -0
  42. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/test_fields.py +0 -0
  43. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/test_forms.py +0 -0
  44. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/test_phonenumber.py +0 -0
  45. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/test_serializers.py +0 -0
  46. {csu-2.3.5 → csu-2.4.0}/src/csu/drf/views.py +0 -0
  47. {csu-2.3.5 → csu-2.4.0}/src/csu/enums.py +0 -0
  48. {csu-2.3.5 → csu-2.4.0}/src/csu/env.py +0 -0
  49. {csu-2.3.5 → csu-2.4.0}/src/csu/exceptions.py +0 -0
  50. {csu-2.3.5 → csu-2.4.0}/src/csu/export.py +0 -0
  51. {csu-2.3.5 → csu-2.4.0}/src/csu/fixups.py +0 -0
  52. {csu-2.3.5 → csu-2.4.0}/src/csu/forms/__init__.py +0 -0
  53. {csu-2.3.5 → csu-2.4.0}/src/csu/forms/crispy.py +0 -0
  54. {csu-2.3.5 → csu-2.4.0}/src/csu/forms/fields.py +0 -0
  55. {csu-2.3.5 → csu-2.4.0}/src/csu/gettext.py +0 -0
  56. {csu-2.3.5 → csu-2.4.0}/src/csu/gettext_lazy.py +0 -0
  57. {csu-2.3.5 → csu-2.4.0}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
  58. {csu-2.3.5 → csu-2.4.0}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
  59. {csu-2.3.5 → csu-2.4.0}/src/csu/logging.py +0 -0
  60. {csu-2.3.5 → csu-2.4.0}/src/csu/management.py +0 -0
  61. {csu-2.3.5 → csu-2.4.0}/src/csu/models.py +0 -0
  62. {csu-2.3.5 → csu-2.4.0}/src/csu/query.py +0 -0
  63. {csu-2.3.5 → csu-2.4.0}/src/csu/routers.py +0 -0
  64. {csu-2.3.5 → csu-2.4.0}/src/csu/templates/api_exception.html +0 -0
  65. {csu-2.3.5 → csu-2.4.0}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
  66. {csu-2.3.5 → csu-2.4.0}/src/csu/test_consts.py +0 -0
  67. {csu-2.3.5 → csu-2.4.0}/src/csu/test_env.py +0 -0
  68. {csu-2.3.5 → csu-2.4.0}/src/csu/test_exceptions.py +0 -0
  69. {csu-2.3.5 → csu-2.4.0}/src/csu/test_logging.py +0 -0
  70. {csu-2.3.5 → csu-2.4.0}/src/csu/test_management.py +0 -0
  71. {csu-2.3.5 → csu-2.4.0}/src/csu/test_service.py +0 -0
  72. {csu-2.3.5 → csu-2.4.0}/src/csu/test_timezones.py +0 -0
  73. {csu-2.3.5 → csu-2.4.0}/src/csu/test_utils.py +0 -0
  74. {csu-2.3.5 → csu-2.4.0}/src/csu/test_xml.py +0 -0
  75. {csu-2.3.5 → csu-2.4.0}/src/csu/timezones.py +0 -0
  76. {csu-2.3.5 → csu-2.4.0}/src/csu/utils.py +0 -0
  77. {csu-2.3.5 → csu-2.4.0}/src/csu/views.py +0 -0
  78. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/__init__.py +0 -0
  79. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/admin.py +0 -0
  80. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/asgi.py +0 -0
  81. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/engine.py +0 -0
  82. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/enums.py +0 -0
  83. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/job.py +0 -0
  84. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/models.py +0 -0
  85. {csu-2.3.5 → csu-2.4.0}/src/csu/worker/registry.py +0 -0
  86. {csu-2.3.5 → csu-2.4.0}/src/csu/wsgi.py +0 -0
  87. {csu-2.3.5 → csu-2.4.0}/src/csu/xml.py +0 -0
  88. {csu-2.3.5 → csu-2.4.0}/src/csu.egg-info/dependency_links.txt +0 -0
  89. {csu-2.3.5 → csu-2.4.0}/src/csu.egg-info/requires.txt +0 -0
  90. {csu-2.3.5 → csu-2.4.0}/src/csu.egg-info/top_level.txt +0 -0
  91. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
  92. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
  93. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_decoding.yaml +0 -0
  94. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_error.yaml +0 -0
  95. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_logging.yaml +0 -0
  96. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service/test_redirects.yaml +0 -0
  97. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
  98. {csu-2.3.5 → csu-2.4.0}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
  99. {csu-2.3.5 → csu-2.4.0}/tests/exampleworker.py +0 -0
  100. {csu-2.3.5 → csu-2.4.0}/tests/test_exampleworker.py +0 -0
  101. {csu-2.3.5 → csu-2.4.0}/tests/test_models.py +0 -0
  102. {csu-2.3.5 → csu-2.4.0}/tests/test_views.py +0 -0
  103. {csu-2.3.5 → csu-2.4.0}/tests/test_worker.py +0 -0
  104. {csu-2.3.5 → csu-2.4.0}/tests/testproject/__init__.py +0 -0
  105. {csu-2.3.5 → csu-2.4.0}/tests/testproject/fixtures/testuser.json +0 -0
  106. {csu-2.3.5 → csu-2.4.0}/tests/testproject/models.py +0 -0
  107. {csu-2.3.5 → csu-2.4.0}/tests/testproject/settings.py +0 -0
  108. {csu-2.3.5 → csu-2.4.0}/tests/testproject/urls.py +0 -0
  109. {csu-2.3.5 → csu-2.4.0}/tox.ini +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.3.5
2
+ current_version = 2.4.0
3
3
  commit = True
4
4
  tag = True
5
5
 
@@ -41,7 +41,7 @@ default_context:
41
41
  sphinx_theme: furo
42
42
  test_matrix_separate_coverage: false
43
43
  tests_inside_package: true
44
- version: 2.3.5
44
+ version: 2.4.0
45
45
  version_manager: bump2version
46
46
  website: https://blog.ionelmc.ro
47
47
  year_from: '2024'
@@ -11,14 +11,14 @@ repos:
11
11
  - id: django-upgrade
12
12
  args: [ --target-version, "5.2" ]
13
13
  - repo: https://github.com/astral-sh/ruff-pre-commit
14
- rev: v0.15.10
14
+ rev: v0.15.17
15
15
  hooks:
16
16
  - id: ruff
17
17
  # Using --unsafe-fixes can change code behavior
18
18
  args: [--fix, --exit-non-zero-on-fix, --show-fixes, --unsafe-fixes]
19
19
  - id: ruff-format
20
20
  - repo: https://github.com/tombi-toml/tombi-pre-commit
21
- rev: v0.9.17
21
+ rev: v1.1.2
22
22
  hooks:
23
23
  - id: tombi-format
24
24
  # args: [ "--offline" ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.3.5
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.3.5.svg
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.3.5...main
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.3.5.svg
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.3.5...main
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csu"
3
- version = "2.3.5"
3
+ version = "2.4.0"
4
4
  description = "Clean Slate Utils - bunch of utility code, mostly Django/DRF specific."
5
5
  requires-python = ">=3.14"
6
6
  license = "BSD-2-Clause"
@@ -2,4 +2,4 @@
2
2
  a.k.a Clean Slate Utils
3
3
  """
4
4
 
5
- __version__ = "2.3.5"
5
+ __version__ = "2.4.0"
@@ -165,7 +165,7 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
165
165
  self.handle_log_response(response, response.content)
166
166
 
167
167
  def handle_log_response(self, response: Response | AsyncResponse, content):
168
- size = len(content)
168
+ size = len(content) if content else 0
169
169
  content_type = response.headers.get("content-type")
170
170
  content_type = "no content-type" if content_type is None else repr(content_type)
171
171
  response_line = f" response body ({content_type} - {size} bytes) ".center(LINE_LENGTH, "-")
@@ -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
- self,
220
- response: Response,
221
- *,
222
- retry_statuses: set[int],
223
- accept_statuses: set[int],
224
- json,
225
- ) -> RTYPE:
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 expect_json:
304
- try:
305
- json = response.json(parse_float=Decimal)
306
- except Exception as exc:
307
- raise DecodeError(response, exc, event_id=self.event_id) from exc
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[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
316
+ class AsyncHTTPServiceContext[RTYPE = HTTPServiceResponse](HTTPServiceContext[RTYPE]):
325
317
  auth_class = AsyncHTTPServiceAuth
326
318
  client_class = AsyncSession
327
319
  client: AsyncSession
@@ -334,7 +326,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
334
326
  async def log_request(self, request: PreparedRequest):
335
327
  self.handle_log_request(request)
336
328
 
337
- async def log_response(self, response: AsyncResponse | Response):
329
+ async def log_response(self, response: AsyncResponse | Response, **kwargs):
338
330
  if isinstance(response, AsyncResponse):
339
331
  content = await response.content
340
332
  else:
@@ -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
- ) -> RT:
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 with less internals coupling
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 expect_json:
394
- try:
395
- if isinstance(response, AsyncResponse):
396
- json = await response.json(parse_float=Decimal)
397
- else:
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.5
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.3.5.svg
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.3.5...main
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