csu 2.3.5__tar.gz → 2.3.6__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 (107) hide show
  1. {csu-2.3.5 → csu-2.3.6}/.bumpversion.cfg +1 -1
  2. {csu-2.3.5 → csu-2.3.6}/.cookiecutterrc +1 -1
  3. {csu-2.3.5 → csu-2.3.6}/.pre-commit-config.yaml +2 -2
  4. {csu-2.3.5 → csu-2.3.6}/PKG-INFO +3 -3
  5. {csu-2.3.5 → csu-2.3.6}/README.rst +2 -2
  6. {csu-2.3.5 → csu-2.3.6}/pyproject.toml +1 -1
  7. {csu-2.3.5 → csu-2.3.6}/src/csu/__init__.py +1 -1
  8. {csu-2.3.5 → csu-2.3.6}/src/csu/service.py +2 -2
  9. {csu-2.3.5 → csu-2.3.6}/src/csu.egg-info/PKG-INFO +3 -3
  10. csu-2.3.6/tests/conftest.py +186 -0
  11. {csu-2.3.5 → csu-2.3.6}/tests/test_service_auth.py +12 -10
  12. csu-2.3.5/tests/conftest.py +0 -56
  13. {csu-2.3.5 → csu-2.3.6}/.coveragerc +0 -0
  14. {csu-2.3.5 → csu-2.3.6}/.editorconfig +0 -0
  15. {csu-2.3.5 → csu-2.3.6}/.github/workflows/github-actions.yml +0 -0
  16. {csu-2.3.5 → csu-2.3.6}/.tombi.toml +0 -0
  17. {csu-2.3.5 → csu-2.3.6}/AUTHORS.rst +0 -0
  18. {csu-2.3.5 → csu-2.3.6}/CHANGELOG.rst +0 -0
  19. {csu-2.3.5 → csu-2.3.6}/CONTRIBUTING.rst +0 -0
  20. {csu-2.3.5 → csu-2.3.6}/LICENSE +0 -0
  21. {csu-2.3.5 → csu-2.3.6}/MANIFEST.in +0 -0
  22. {csu-2.3.5 → csu-2.3.6}/ci/bootstrap.py +0 -0
  23. {csu-2.3.5 → csu-2.3.6}/ci/requirements.txt +0 -0
  24. {csu-2.3.5 → csu-2.3.6}/ci/templates/.github/workflows/github-actions.yml +0 -0
  25. {csu-2.3.5 → csu-2.3.6}/pytest.ini +0 -0
  26. {csu-2.3.5 → csu-2.3.6}/setup.cfg +0 -0
  27. {csu-2.3.5 → csu-2.3.6}/src/csu/admin.py +0 -0
  28. {csu-2.3.5 → csu-2.3.6}/src/csu/conf.py +0 -0
  29. {csu-2.3.5 → csu-2.3.6}/src/csu/consts.py +0 -0
  30. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/__init__.py +0 -0
  31. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/auth.py +0 -0
  32. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/fields.py +0 -0
  33. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/forms.py +0 -0
  34. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/perms.py +0 -0
  35. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/phonenumber.py +0 -0
  36. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/serializers.py +0 -0
  37. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/test_auth.py +0 -0
  38. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/test_fields.py +0 -0
  39. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/test_forms.py +0 -0
  40. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/test_phonenumber.py +0 -0
  41. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/test_serializers.py +0 -0
  42. {csu-2.3.5 → csu-2.3.6}/src/csu/drf/views.py +0 -0
  43. {csu-2.3.5 → csu-2.3.6}/src/csu/enums.py +0 -0
  44. {csu-2.3.5 → csu-2.3.6}/src/csu/env.py +0 -0
  45. {csu-2.3.5 → csu-2.3.6}/src/csu/exceptions.py +0 -0
  46. {csu-2.3.5 → csu-2.3.6}/src/csu/export.py +0 -0
  47. {csu-2.3.5 → csu-2.3.6}/src/csu/fixups.py +0 -0
  48. {csu-2.3.5 → csu-2.3.6}/src/csu/forms/__init__.py +0 -0
  49. {csu-2.3.5 → csu-2.3.6}/src/csu/forms/crispy.py +0 -0
  50. {csu-2.3.5 → csu-2.3.6}/src/csu/forms/fields.py +0 -0
  51. {csu-2.3.5 → csu-2.3.6}/src/csu/gettext.py +0 -0
  52. {csu-2.3.5 → csu-2.3.6}/src/csu/gettext_lazy.py +0 -0
  53. {csu-2.3.5 → csu-2.3.6}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
  54. {csu-2.3.5 → csu-2.3.6}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
  55. {csu-2.3.5 → csu-2.3.6}/src/csu/logging.py +0 -0
  56. {csu-2.3.5 → csu-2.3.6}/src/csu/management.py +0 -0
  57. {csu-2.3.5 → csu-2.3.6}/src/csu/models.py +0 -0
  58. {csu-2.3.5 → csu-2.3.6}/src/csu/query.py +0 -0
  59. {csu-2.3.5 → csu-2.3.6}/src/csu/routers.py +0 -0
  60. {csu-2.3.5 → csu-2.3.6}/src/csu/templates/api_exception.html +0 -0
  61. {csu-2.3.5 → csu-2.3.6}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
  62. {csu-2.3.5 → csu-2.3.6}/src/csu/test_consts.py +0 -0
  63. {csu-2.3.5 → csu-2.3.6}/src/csu/test_env.py +0 -0
  64. {csu-2.3.5 → csu-2.3.6}/src/csu/test_exceptions.py +0 -0
  65. {csu-2.3.5 → csu-2.3.6}/src/csu/test_logging.py +0 -0
  66. {csu-2.3.5 → csu-2.3.6}/src/csu/test_management.py +0 -0
  67. {csu-2.3.5 → csu-2.3.6}/src/csu/test_service.py +0 -0
  68. {csu-2.3.5 → csu-2.3.6}/src/csu/test_timezones.py +0 -0
  69. {csu-2.3.5 → csu-2.3.6}/src/csu/test_utils.py +0 -0
  70. {csu-2.3.5 → csu-2.3.6}/src/csu/test_xml.py +0 -0
  71. {csu-2.3.5 → csu-2.3.6}/src/csu/timezones.py +0 -0
  72. {csu-2.3.5 → csu-2.3.6}/src/csu/utils.py +0 -0
  73. {csu-2.3.5 → csu-2.3.6}/src/csu/views.py +0 -0
  74. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/__init__.py +0 -0
  75. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/admin.py +0 -0
  76. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/asgi.py +0 -0
  77. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/engine.py +0 -0
  78. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/enums.py +0 -0
  79. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/job.py +0 -0
  80. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/models.py +0 -0
  81. {csu-2.3.5 → csu-2.3.6}/src/csu/worker/registry.py +0 -0
  82. {csu-2.3.5 → csu-2.3.6}/src/csu/wsgi.py +0 -0
  83. {csu-2.3.5 → csu-2.3.6}/src/csu/xml.py +0 -0
  84. {csu-2.3.5 → csu-2.3.6}/src/csu.egg-info/SOURCES.txt +0 -0
  85. {csu-2.3.5 → csu-2.3.6}/src/csu.egg-info/dependency_links.txt +0 -0
  86. {csu-2.3.5 → csu-2.3.6}/src/csu.egg-info/requires.txt +0 -0
  87. {csu-2.3.5 → csu-2.3.6}/src/csu.egg-info/top_level.txt +0 -0
  88. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
  89. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
  90. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_decoding.yaml +0 -0
  91. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_error.yaml +0 -0
  92. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_logging.yaml +0 -0
  93. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service/test_redirects.yaml +0 -0
  94. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
  95. {csu-2.3.5 → csu-2.3.6}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
  96. {csu-2.3.5 → csu-2.3.6}/tests/exampleworker.py +0 -0
  97. {csu-2.3.5 → csu-2.3.6}/tests/test_exampleworker.py +0 -0
  98. {csu-2.3.5 → csu-2.3.6}/tests/test_models.py +0 -0
  99. {csu-2.3.5 → csu-2.3.6}/tests/test_service.py +0 -0
  100. {csu-2.3.5 → csu-2.3.6}/tests/test_views.py +0 -0
  101. {csu-2.3.5 → csu-2.3.6}/tests/test_worker.py +0 -0
  102. {csu-2.3.5 → csu-2.3.6}/tests/testproject/__init__.py +0 -0
  103. {csu-2.3.5 → csu-2.3.6}/tests/testproject/fixtures/testuser.json +0 -0
  104. {csu-2.3.5 → csu-2.3.6}/tests/testproject/models.py +0 -0
  105. {csu-2.3.5 → csu-2.3.6}/tests/testproject/settings.py +0 -0
  106. {csu-2.3.5 → csu-2.3.6}/tests/testproject/urls.py +0 -0
  107. {csu-2.3.5 → csu-2.3.6}/tox.ini +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.3.5
2
+ current_version = 2.3.6
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.3.6
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.3.6
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.3.6.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.3.6...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.3.6.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.3.6...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.3.6"
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.3.6"
@@ -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, "-")
@@ -334,7 +334,7 @@ class AsyncHTTPServiceContext[RT = HTTPServiceResponse](HTTPServiceContext[RT]):
334
334
  async def log_request(self, request: PreparedRequest):
335
335
  self.handle_log_request(request)
336
336
 
337
- async def log_response(self, response: AsyncResponse | Response):
337
+ async def log_response(self, response: AsyncResponse | Response, **kwargs):
338
338
  if isinstance(response, AsyncResponse):
339
339
  content = await response.content
340
340
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csu
3
- Version: 2.3.5
3
+ Version: 2.3.6
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.3.6.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.3.6...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/
@@ -0,0 +1,186 @@
1
+ import functools
2
+ import itertools
3
+ import sys
4
+ import time
5
+ from datetime import datetime
6
+ from io import BytesIO
7
+ from logging import INFO
8
+ from logging import getLogger
9
+ from pathlib import Path
10
+ from types import SimpleNamespace
11
+ from unittest import mock
12
+
13
+ import pytest
14
+ from niquests import adapters
15
+ from niquests import async_session
16
+ from niquests import sessions
17
+ from urllib3 import AsyncHTTPConnectionPool
18
+ from urllib3 import AsyncHTTPResponse
19
+ from urllib3 import HTTPHeaderDict
20
+ from vcr.errors import CannotOverwriteExistingCassetteException
21
+ from vcr.patch import CassettePatcherBuilder
22
+ from vcr.request import Request
23
+
24
+ from csu import exceptions
25
+
26
+ logger = getLogger(__name__)
27
+
28
+ _original_async_urlopen = AsyncHTTPConnectionPool.urlopen
29
+ _original_build = CassettePatcherBuilder.build
30
+
31
+ _DEFAULT_PORTS = {"https": 443, "http": 80}
32
+
33
+
34
+ @pytest.fixture(scope="session")
35
+ def vcr_config():
36
+ return {
37
+ "decode_compressed_response": True,
38
+ "match_on": ("method", "scheme", "host", "path", "query"), # no port because it's random
39
+ }
40
+
41
+
42
+ @pytest.fixture
43
+ def fake_accident_id(monkeypatch):
44
+ calls = []
45
+
46
+ def fake_urandom(_):
47
+ frame = sys._getframe().f_back.f_back
48
+ filename = Path(frame.f_code.co_filename).name
49
+ calls.append(f"{filename}:{frame.f_lineno}")
50
+ return bytes([len(calls)])
51
+
52
+ monkeypatch.setattr(exceptions, "now", lambda: datetime(2024, 12, 12)) # noqa: DTZ001
53
+ monkeypatch.setattr(exceptions, "urandom", fake_urandom)
54
+ monkeypatch.setattr(time, "perf_counter", lambda: 0)
55
+ monkeypatch.setattr(sessions, "preferred_clock", lambda: 0)
56
+
57
+ return SimpleNamespace(
58
+ calls=calls,
59
+ )
60
+
61
+
62
+ def replace(text, replacements: dict[str, str]):
63
+ for alias, value in replacements.items():
64
+ text = text.replace(value, f"[&{alias}]")
65
+ return text
66
+
67
+
68
+ @pytest.fixture
69
+ def caplogs(caplog):
70
+ def get_logs(**replacements):
71
+ return [replace(f"{record.levelname:7} | {record.message}", replacements) for record in caplog.records]
72
+
73
+ with caplog.at_level(INFO, logger="service"):
74
+ yield SimpleNamespace(
75
+ get=get_logs,
76
+ )
77
+
78
+
79
+ def _serialize_headers(headers) -> dict[str, list[str]]:
80
+ out: dict[str, list[str]] = {}
81
+ for key, val in headers.items():
82
+ out.setdefault(key, []).append(val)
83
+ return out
84
+
85
+
86
+ def _deserialize_headers(headers: dict[str, list[str]]) -> HTTPHeaderDict:
87
+ hd = HTTPHeaderDict()
88
+ for key, values in headers.items():
89
+ if isinstance(values, list):
90
+ for v in values:
91
+ hd.add(key, v)
92
+ else:
93
+ hd.add(key, values)
94
+ return hd
95
+
96
+
97
+ def _reconstruct_uri(pool, url: str) -> str:
98
+ if url.startswith(("http://", "https://")):
99
+ return url
100
+ scheme = pool.scheme or "http"
101
+ host = pool.host
102
+ port = pool.port
103
+ port_postfix = f":{port}" if port and port != _DEFAULT_PORTS.get(scheme) else ""
104
+ return f"{scheme}://{host}{port_postfix}{url}"
105
+
106
+
107
+ def _build_async_response(vcr_response) -> AsyncHTTPResponse:
108
+ headers = dict(vcr_response.get("headers", {}))
109
+ for k in [h for h in headers if h.lower() == "transfer-encoding"]:
110
+ del headers[k]
111
+ body = vcr_response.get("body", {}).get("string", b"")
112
+ if isinstance(body, str):
113
+ body = body.encode("utf-8")
114
+ return AsyncHTTPResponse(
115
+ body=BytesIO(body),
116
+ status=vcr_response["status"]["code"],
117
+ reason=vcr_response["status"]["message"],
118
+ headers=_deserialize_headers(headers),
119
+ preload_content=True,
120
+ )
121
+
122
+
123
+ def _make_async_urlopen(cassette):
124
+ @functools.wraps(_original_async_urlopen)
125
+ async def vcr_async_urlopen(self, method, url, body=None, headers=None, **kw):
126
+ if headers is None:
127
+ headers = {}
128
+ uri = _reconstruct_uri(self, url)
129
+ vcr_request = Request(method, uri, body, _serialize_headers(headers))
130
+
131
+ if cassette.can_play_response_for(vcr_request):
132
+ logger.info("Playing response for %s from cassette", vcr_request)
133
+ return _build_async_response(cassette.play_response(vcr_request))
134
+
135
+ if cassette.write_protected and cassette.filter_request(vcr_request):
136
+ raise CannotOverwriteExistingCassetteException(
137
+ cassette=cassette,
138
+ failed_request=vcr_request,
139
+ )
140
+
141
+ logger.info("%s not in cassette, sending to real server", vcr_request)
142
+ response = await _original_async_urlopen(self, method, url, body=body, headers=headers, **kw)
143
+ resp_body = response._body
144
+ if resp_body is None:
145
+ resp_body = await response.data
146
+ if isinstance(resp_body, str):
147
+ resp_body = resp_body.encode("utf-8")
148
+ cassette.append(
149
+ vcr_request,
150
+ {
151
+ "status": {"code": response.status, "message": response.reason or ""},
152
+ "headers": _serialize_headers(response.headers),
153
+ "body": {"string": resp_body or b""},
154
+ },
155
+ )
156
+ return response
157
+
158
+ return vcr_async_urlopen
159
+
160
+
161
+ def _patched_build(self):
162
+ return itertools.chain(
163
+ _original_build(self),
164
+ (
165
+ mock.patch.object(
166
+ AsyncHTTPConnectionPool,
167
+ "urlopen",
168
+ _make_async_urlopen(self._cassette),
169
+ ),
170
+ ),
171
+ )
172
+
173
+
174
+ @pytest.fixture(scope="session", autouse=True)
175
+ def _vcr_async_urllib3():
176
+ """Monkey-patch vcrpy so cassettes also cover async urllib3."""
177
+ CassettePatcherBuilder.build = _patched_build
178
+ yield
179
+ CassettePatcherBuilder.build = _original_build
180
+
181
+
182
+ @pytest.fixture(autouse=True)
183
+ def vcr_fix(monkeypatch):
184
+ monkeypatch.setattr(adapters, "preferred_clock", lambda: 0)
185
+ monkeypatch.setattr(sessions, "preferred_clock", lambda: 0)
186
+ monkeypatch.setattr(async_session, "preferred_clock", lambda: 0)
@@ -124,23 +124,24 @@ INFO | [01:000000.00] POST [&HTTPBIN]/post
124
124
  Connection: 'keep-alive'
125
125
  Content-Length: '15'
126
126
  Content-Type: 'application/json;charset=utf-8'
127
- User-Agent: 'niquests/3.18.1'
128
- ------------------------------------------------------------- request content --------------------------------------------------------------
127
+ User-Agent: 'niquests/3.18.6'
128
+ ---------------------------------------- request body ('application/json;charset=utf-8' - 15 bytes) ----------------------------------------
129
129
  b'{"foobar": 123}'
130
130
  ============================================================================================================================================\
131
131
  """,
132
+ "INFO | Playing response for <Request (POST) [&HTTPBIN]/post> from cassette",
132
133
  """\
133
- INFO | [01:000000.00] POST [&HTTPBIN]/post => 200 in 0.0105s (H11)
134
+ INFO | [01:000000.00] POST [&HTTPBIN]/post => 200 in 0.0000s (H0)
134
135
  ------------------------------------------------------------- response headers -------------------------------------------------------------
135
136
  Access-Control-Allow-Credentials: 'true'
136
137
  Access-Control-Allow-Origin: '*'
137
138
  Connection: 'Close'
138
139
  Content-Length: '356'
139
140
  Content-Type: 'application/json'
140
- Date: 'Thu, 12 Mar 2026 20:29:34 GMT'
141
+ Date: 'Thu, 12 Mar 2026 20:21:07 GMT'
141
142
  Server: 'Pytest-HTTPBIN/0.1.0'
142
- ---------------------------------------------- response body ('application/json' - 356) bytes ----------------------------------------------
143
- b'{"args":{},"data":"{\\\\"foobar\\\\": 123}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"15","Content-Type":"application/json;charset=utf-8","Host":"127.0.0.1:35321","User-Agent":"niquests/3.18.1"},"json":{"foobar":123},"origin":"127.0.0.1","url":"[&HTTPBIN]/post"}\\n'
143
+ ---------------------------------------------- response body ('application/json' - 356 bytes) ----------------------------------------------
144
+ b'{"args":{},"data":"{\\\\"foobar\\\\": 123}","files":{},"form":{},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Connection":"keep-alive","Content-Length":"15","Content-Type":"application/json;charset=utf-8","Host":"127.0.0.1:44101","User-Agent":"niquests/3.18.1"},"json":{"foobar":123},"origin":"127.0.0.1","url":"http://127.0.0.1:44101/post"}\\n'
144
145
  ============================================================================================================================================\
145
146
  """,
146
147
  """\
@@ -149,20 +150,21 @@ INFO | [01:000000.00] GET [&HTTPBIN]/json
149
150
  Accept: '*/*'
150
151
  Accept-Encoding: 'gzip, deflate, br'
151
152
  Connection: 'keep-alive'
152
- User-Agent: 'niquests/3.18.1'
153
+ User-Agent: 'niquests/3.18.6'
153
154
  X-Auth: '123'\
154
155
  """,
156
+ "INFO | Playing response for <Request (GET) [&HTTPBIN]/json> from cassette",
155
157
  """\
156
- INFO | [01:000000.00] GET [&HTTPBIN]/json => 200 in 0.0021s (H11)
158
+ INFO | [01:000000.00] GET [&HTTPBIN]/json => 200 in 0.0000s (H0)
157
159
  ------------------------------------------------------------- response headers -------------------------------------------------------------
158
160
  Access-Control-Allow-Credentials: 'true'
159
161
  Access-Control-Allow-Origin: '*'
160
162
  Connection: 'Close'
161
163
  Content-Length: '275'
162
164
  Content-Type: 'application/json'
163
- Date: 'Thu, 12 Mar 2026 20:29:34 GMT'
165
+ Date: 'Thu, 12 Mar 2026 20:21:07 GMT'
164
166
  Server: 'Pytest-HTTPBIN/0.1.0'
165
- ---------------------------------------------- response body ('application/json' - 275) bytes ----------------------------------------------
167
+ ---------------------------------------------- response body ('application/json' - 275 bytes) ----------------------------------------------
166
168
  b'{"slideshow":{"author":"Yours Truly","date":"date of publication","slides":[{"title":"Wake up to WonderWidgets!","type":"all"},{"items":["Why <em>WonderWidgets</em> are great","Who <em>buys</em> WonderWidgets"],"title":"Overview","type":"all"}],"title":"Sample Slide Show"}}\\n'
167
169
  ============================================================================================================================================\
168
170
  """,
@@ -1,56 +0,0 @@
1
- import logging
2
- import sys
3
- import time
4
- from datetime import datetime
5
- from pathlib import Path
6
- from types import SimpleNamespace
7
-
8
- import pytest
9
- from niquests import sessions
10
-
11
- from csu import exceptions
12
-
13
-
14
- @pytest.fixture(scope="session")
15
- def vcr_config():
16
- return {
17
- "decode_compressed_response": True,
18
- "match_on": ("method", "scheme", "host", "path", "query"), # no port because it's random
19
- }
20
-
21
-
22
- @pytest.fixture
23
- def fake_accident_id(monkeypatch):
24
- calls = []
25
-
26
- def fake_urandom(_):
27
- frame = sys._getframe().f_back.f_back
28
- filename = Path(frame.f_code.co_filename).name
29
- calls.append(f"{filename}:{frame.f_lineno}")
30
- return bytes([len(calls)])
31
-
32
- monkeypatch.setattr(exceptions, "now", lambda: datetime(2024, 12, 12)) # noqa: DTZ001
33
- monkeypatch.setattr(exceptions, "urandom", fake_urandom)
34
- monkeypatch.setattr(time, "perf_counter", lambda: 0)
35
- monkeypatch.setattr(sessions, "preferred_clock", lambda: 0)
36
-
37
- return SimpleNamespace(
38
- calls=calls,
39
- )
40
-
41
-
42
- def replace(text, replacements: dict[str, str]):
43
- for alias, value in replacements.items():
44
- text = text.replace(value, f"[&{alias}]")
45
- return text
46
-
47
-
48
- @pytest.fixture
49
- def caplogs(caplog):
50
- def get_logs(**replacements):
51
- return [replace(f"{record.levelname:7} | {record.message}", replacements) for record in caplog.records]
52
-
53
- with caplog.at_level(logging.INFO, logger="service"):
54
- yield SimpleNamespace(
55
- get=get_logs,
56
- )
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