csu 2.3.4__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.4 → csu-2.3.6}/.bumpversion.cfg +1 -1
  2. {csu-2.3.4 → csu-2.3.6}/.cookiecutterrc +1 -1
  3. {csu-2.3.4 → csu-2.3.6}/.pre-commit-config.yaml +2 -2
  4. {csu-2.3.4 → csu-2.3.6}/PKG-INFO +3 -3
  5. {csu-2.3.4 → csu-2.3.6}/README.rst +2 -2
  6. {csu-2.3.4 → csu-2.3.6}/pyproject.toml +1 -1
  7. {csu-2.3.4 → csu-2.3.6}/src/csu/__init__.py +1 -1
  8. {csu-2.3.4 → csu-2.3.6}/src/csu/service.py +3 -3
  9. {csu-2.3.4 → 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.4 → csu-2.3.6}/tests/test_service.py +9 -9
  12. {csu-2.3.4 → csu-2.3.6}/tests/test_service_auth.py +14 -12
  13. {csu-2.3.4 → csu-2.3.6}/tox.ini +1 -0
  14. csu-2.3.4/tests/conftest.py +0 -56
  15. {csu-2.3.4 → csu-2.3.6}/.coveragerc +0 -0
  16. {csu-2.3.4 → csu-2.3.6}/.editorconfig +0 -0
  17. {csu-2.3.4 → csu-2.3.6}/.github/workflows/github-actions.yml +0 -0
  18. {csu-2.3.4 → csu-2.3.6}/.tombi.toml +0 -0
  19. {csu-2.3.4 → csu-2.3.6}/AUTHORS.rst +0 -0
  20. {csu-2.3.4 → csu-2.3.6}/CHANGELOG.rst +0 -0
  21. {csu-2.3.4 → csu-2.3.6}/CONTRIBUTING.rst +0 -0
  22. {csu-2.3.4 → csu-2.3.6}/LICENSE +0 -0
  23. {csu-2.3.4 → csu-2.3.6}/MANIFEST.in +0 -0
  24. {csu-2.3.4 → csu-2.3.6}/ci/bootstrap.py +0 -0
  25. {csu-2.3.4 → csu-2.3.6}/ci/requirements.txt +0 -0
  26. {csu-2.3.4 → csu-2.3.6}/ci/templates/.github/workflows/github-actions.yml +0 -0
  27. {csu-2.3.4 → csu-2.3.6}/pytest.ini +0 -0
  28. {csu-2.3.4 → csu-2.3.6}/setup.cfg +0 -0
  29. {csu-2.3.4 → csu-2.3.6}/src/csu/admin.py +0 -0
  30. {csu-2.3.4 → csu-2.3.6}/src/csu/conf.py +0 -0
  31. {csu-2.3.4 → csu-2.3.6}/src/csu/consts.py +0 -0
  32. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/__init__.py +0 -0
  33. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/auth.py +0 -0
  34. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/fields.py +0 -0
  35. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/forms.py +0 -0
  36. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/perms.py +0 -0
  37. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/phonenumber.py +0 -0
  38. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/serializers.py +0 -0
  39. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/test_auth.py +0 -0
  40. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/test_fields.py +0 -0
  41. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/test_forms.py +0 -0
  42. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/test_phonenumber.py +0 -0
  43. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/test_serializers.py +0 -0
  44. {csu-2.3.4 → csu-2.3.6}/src/csu/drf/views.py +0 -0
  45. {csu-2.3.4 → csu-2.3.6}/src/csu/enums.py +0 -0
  46. {csu-2.3.4 → csu-2.3.6}/src/csu/env.py +0 -0
  47. {csu-2.3.4 → csu-2.3.6}/src/csu/exceptions.py +0 -0
  48. {csu-2.3.4 → csu-2.3.6}/src/csu/export.py +0 -0
  49. {csu-2.3.4 → csu-2.3.6}/src/csu/fixups.py +0 -0
  50. {csu-2.3.4 → csu-2.3.6}/src/csu/forms/__init__.py +0 -0
  51. {csu-2.3.4 → csu-2.3.6}/src/csu/forms/crispy.py +0 -0
  52. {csu-2.3.4 → csu-2.3.6}/src/csu/forms/fields.py +0 -0
  53. {csu-2.3.4 → csu-2.3.6}/src/csu/gettext.py +0 -0
  54. {csu-2.3.4 → csu-2.3.6}/src/csu/gettext_lazy.py +0 -0
  55. {csu-2.3.4 → csu-2.3.6}/src/csu/locale/ro/LC_MESSAGES/django.mo +0 -0
  56. {csu-2.3.4 → csu-2.3.6}/src/csu/locale/ro/LC_MESSAGES/django.po +0 -0
  57. {csu-2.3.4 → csu-2.3.6}/src/csu/logging.py +0 -0
  58. {csu-2.3.4 → csu-2.3.6}/src/csu/management.py +0 -0
  59. {csu-2.3.4 → csu-2.3.6}/src/csu/models.py +0 -0
  60. {csu-2.3.4 → csu-2.3.6}/src/csu/query.py +0 -0
  61. {csu-2.3.4 → csu-2.3.6}/src/csu/routers.py +0 -0
  62. {csu-2.3.4 → csu-2.3.6}/src/csu/templates/api_exception.html +0 -0
  63. {csu-2.3.4 → csu-2.3.6}/src/csu/templates/forms/widgets/opaquewidget.html +0 -0
  64. {csu-2.3.4 → csu-2.3.6}/src/csu/test_consts.py +0 -0
  65. {csu-2.3.4 → csu-2.3.6}/src/csu/test_env.py +0 -0
  66. {csu-2.3.4 → csu-2.3.6}/src/csu/test_exceptions.py +0 -0
  67. {csu-2.3.4 → csu-2.3.6}/src/csu/test_logging.py +0 -0
  68. {csu-2.3.4 → csu-2.3.6}/src/csu/test_management.py +0 -0
  69. {csu-2.3.4 → csu-2.3.6}/src/csu/test_service.py +0 -0
  70. {csu-2.3.4 → csu-2.3.6}/src/csu/test_timezones.py +0 -0
  71. {csu-2.3.4 → csu-2.3.6}/src/csu/test_utils.py +0 -0
  72. {csu-2.3.4 → csu-2.3.6}/src/csu/test_xml.py +0 -0
  73. {csu-2.3.4 → csu-2.3.6}/src/csu/timezones.py +0 -0
  74. {csu-2.3.4 → csu-2.3.6}/src/csu/utils.py +0 -0
  75. {csu-2.3.4 → csu-2.3.6}/src/csu/views.py +0 -0
  76. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/__init__.py +0 -0
  77. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/admin.py +0 -0
  78. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/asgi.py +0 -0
  79. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/engine.py +0 -0
  80. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/enums.py +0 -0
  81. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/job.py +0 -0
  82. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/models.py +0 -0
  83. {csu-2.3.4 → csu-2.3.6}/src/csu/worker/registry.py +0 -0
  84. {csu-2.3.4 → csu-2.3.6}/src/csu/wsgi.py +0 -0
  85. {csu-2.3.4 → csu-2.3.6}/src/csu/xml.py +0 -0
  86. {csu-2.3.4 → csu-2.3.6}/src/csu.egg-info/SOURCES.txt +0 -0
  87. {csu-2.3.4 → csu-2.3.6}/src/csu.egg-info/dependency_links.txt +0 -0
  88. {csu-2.3.4 → csu-2.3.6}/src/csu.egg-info/requires.txt +0 -0
  89. {csu-2.3.4 → csu-2.3.6}/src/csu.egg-info/top_level.txt +0 -0
  90. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_custom_context.yaml +0 -0
  91. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_custom_context_0_retries.yaml +0 -0
  92. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_decoding.yaml +0 -0
  93. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_error.yaml +0 -0
  94. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_logging.yaml +0 -0
  95. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service/test_redirects.yaml +0 -0
  96. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service_auth/test_async.yaml +0 -0
  97. {csu-2.3.4 → csu-2.3.6}/tests/cassettes/test_service_auth/test_sync.yaml +0 -0
  98. {csu-2.3.4 → csu-2.3.6}/tests/exampleworker.py +0 -0
  99. {csu-2.3.4 → csu-2.3.6}/tests/test_exampleworker.py +0 -0
  100. {csu-2.3.4 → csu-2.3.6}/tests/test_models.py +0 -0
  101. {csu-2.3.4 → csu-2.3.6}/tests/test_views.py +0 -0
  102. {csu-2.3.4 → csu-2.3.6}/tests/test_worker.py +0 -0
  103. {csu-2.3.4 → csu-2.3.6}/tests/testproject/__init__.py +0 -0
  104. {csu-2.3.4 → csu-2.3.6}/tests/testproject/fixtures/testuser.json +0 -0
  105. {csu-2.3.4 → csu-2.3.6}/tests/testproject/models.py +0 -0
  106. {csu-2.3.4 → csu-2.3.6}/tests/testproject/settings.py +0 -0
  107. {csu-2.3.4 → csu-2.3.6}/tests/testproject/urls.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.3.4
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.4
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.4
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.4.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.4...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.4.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.4...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.4"
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.4"
5
+ __version__ = "2.3.6"
@@ -161,11 +161,11 @@ class HTTPServiceContext[RTYPE = HTTPServiceResponse]:
161
161
  THICK_LINE,
162
162
  )
163
163
 
164
- def log_response(self, response: Response):
164
+ def log_response(self, response: Response, **kwargs):
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.4
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.4.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.4...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)
@@ -174,7 +174,7 @@ INFO | [01:000000.00] GET [&HTTPBIN]/status/599
174
174
  Accept: '*/*'
175
175
  Accept-Encoding: 'gzip, deflate, br'
176
176
  Connection: 'keep-alive'
177
- User-Agent: 'niquests/3.18.1'\
177
+ User-Agent: 'niquests/3.18.6'\
178
178
  """,
179
179
  """\
180
180
  INFO | [01:000000.00] GET [&HTTPBIN]/status/599 => 599 in 0.0000s (HNone)
@@ -221,7 +221,7 @@ INFO | [01:000000.00] GET [&HTTPBIN]/status/418
221
221
  Accept: '*/*'
222
222
  Accept-Encoding: 'gzip, deflate, br'
223
223
  Connection: 'keep-alive'
224
- User-Agent: 'niquests/3.18.1'\
224
+ User-Agent: 'niquests/3.18.6'\
225
225
  """,
226
226
  '''\
227
227
  INFO | [01:000000.00] GET [&HTTPBIN]/status/418 => 418 in 0.0000s (HNone)
@@ -245,7 +245,7 @@ ERROR | (renderer: NoneType)
245
245
  {}
246
246
  ---------------------------------------------------------------- traceback -----------------------------------------------------------------
247
247
  Traceback (most recent call last):
248
- File "[&MODELS_PATH]", line 1526, in json
248
+ File "[&MODELS_PATH]", line 1527, in json
249
249
  return _json.loads(str(encoding_guess), **kwargs)
250
250
  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
251
251
  File "[&JSON_PATH]", line 365, in loads
@@ -263,7 +263,7 @@ During handling of the above exception, another exception occurred:
263
263
  Traceback (most recent call last):
264
264
  File "[&SERVICE_PATH]", line 305, in request
265
265
  json = response.json(parse_float=Decimal)
266
- File "[&MODELS_PATH]", line 1528, in json
266
+ File "[&MODELS_PATH]", line 1529, in json
267
267
  raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
268
268
  niquests.exceptions.JSONDecodeError: Expecting value: line 2 column 5 (char 5)
269
269
 
@@ -312,7 +312,7 @@ INFO | [01:000000.00] GET [&HTTPBIN]/status/599
312
312
  Accept: '*/*'
313
313
  Accept-Encoding: 'gzip, deflate, br'
314
314
  Connection: 'keep-alive'
315
- User-Agent: 'niquests/3.18.1'\
315
+ User-Agent: 'niquests/3.18.6'\
316
316
  """,
317
317
  """\
318
318
  INFO | [01:000000.00] GET [&HTTPBIN]/status/599 => 599 in 0.0000s (HNone)
@@ -520,7 +520,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
520
520
  Accept-Encoding: 'gzip, deflate, br'
521
521
  Connection: 'keep-alive'
522
522
  Content-Length: '27'
523
- User-Agent: 'niquests/3.18.1'
523
+ User-Agent: 'niquests/3.18.6'
524
524
  ------------------------------------------------ request body (no content-type - 27 bytes) -------------------------------------------------
525
525
  "config={'custom': 'config'}"
526
526
  ============================================================================================================================================\
@@ -557,7 +557,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
557
557
  Accept-Encoding: 'gzip, deflate, br'
558
558
  Connection: 'keep-alive'
559
559
  Content-Length: '27'
560
- User-Agent: 'niquests/3.18.1'
560
+ User-Agent: 'niquests/3.18.6'
561
561
  ------------------------------------------------ request body (no content-type - 27 bytes) -------------------------------------------------
562
562
  "config={'custom': 'config'}"
563
563
  ============================================================================================================================================\
@@ -594,7 +594,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
594
594
  Accept-Encoding: 'gzip, deflate, br'
595
595
  Connection: 'keep-alive'
596
596
  Content-Length: '27'
597
- User-Agent: 'niquests/3.18.1'
597
+ User-Agent: 'niquests/3.18.6'
598
598
  ------------------------------------------------ request body (no content-type - 27 bytes) -------------------------------------------------
599
599
  "config={'custom': 'config'}"
600
600
  ============================================================================================================================================\
@@ -631,7 +631,7 @@ INFO | [01:000000.00] POST [&HTTPBIN]/anything?foo=bar&accept_statuses=%7B999
631
631
  Accept-Encoding: 'gzip, deflate, br'
632
632
  Connection: 'keep-alive'
633
633
  Content-Length: '27'
634
- User-Agent: 'niquests/3.18.1'
634
+ User-Agent: 'niquests/3.18.6'
635
635
  ------------------------------------------------ request body (no content-type - 27 bytes) -------------------------------------------------
636
636
  "config={'custom': 'config'}"
637
637
  ============================================================================================================================================\
@@ -59,7 +59,7 @@ INFO | [01:000000.00] POST http://[&HTTPBIN]/post
59
59
  Connection: 'keep-alive'
60
60
  Content-Length: '15'
61
61
  Content-Type: 'application/json;charset=utf-8'
62
- User-Agent: 'niquests/3.18.1'
62
+ User-Agent: 'niquests/3.18.6'
63
63
  ---------------------------------------- request body ('application/json;charset=utf-8' - 15 bytes) ----------------------------------------
64
64
  b'{"foobar": 123}'
65
65
  ============================================================================================================================================\
@@ -84,7 +84,7 @@ INFO | [01:000000.00] GET http://[&HTTPBIN]/json
84
84
  Accept: '*/*'
85
85
  Accept-Encoding: 'gzip, deflate, br'
86
86
  Connection: 'keep-alive'
87
- User-Agent: 'niquests/3.18.1'
87
+ User-Agent: 'niquests/3.18.6'
88
88
  X-Auth: '123'\
89
89
  """,
90
90
  """\
@@ -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
  """,
@@ -35,6 +35,7 @@ deps =
35
35
  pytest-django
36
36
  pytest-httpbin
37
37
  pytest-recording
38
+ https://github.com/kevin1024/vcrpy/archive/30a721d400b55ced25f5d8a612b8a1ae01240160.zip
38
39
  process-tests
39
40
  inline-snapshot
40
41
  time-machine
@@ -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