pytest_httpserver 1.1.1__tar.gz → 1.1.3__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.
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/CHANGES.rst +28 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/PKG-INFO +2 -3
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/conf.py +1 -1
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pyproject.toml +5 -5
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/blocking_httpserver.py +2 -2
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/httpserver.py +59 -41
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_release.py +5 -1
- pytest_httpserver-1.1.3/tests/test_thread_type.py +21 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_threaded.py +1 -1
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/CONTRIBUTION.md +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/LICENSE +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/README.md +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/Makefile +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/_static/.placeholder +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/api.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/background.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/changes.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/fixtures.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/guide.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/howto.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/index.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/patch.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/tutorial.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/doc/upgrade.rst +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/example.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/example_pytest.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/__init__.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/hooks.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/py.typed +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/pytest_plugin.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/Makefile +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/README +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/rootCA.cnf +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/rootCA.crt +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/rootCA.key +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/rootCA.srl +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/server.cnf +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/server.crt +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/server.csr +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/server.key +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/assets/v3.ext +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/conftest.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_example_blocking_httpserver.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_example_query_params1.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_example_query_params2.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_authorization_headers.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_case_insensitive_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_check.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_check_handler_errors.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_custom_handler.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_custom_hooks.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_custom_request_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_header_value_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_hooks.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_json_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_log_querying.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_query_params_dict.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_query_params_never_do_this.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_query_params_proper_use.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_regexp.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_timeout_requests.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_url_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_wait_success.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_blocking_httpserver.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_handler_errors.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_headers.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_hooks.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_ip_protocols.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_json_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_log_leak.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_log_querying.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_matcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_mixed.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_oneshot.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_ordered.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_parse_qs.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_permanent.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_port_changing.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_querymatcher.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_querystring.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_ssl.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_urimatch.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_wait.py +0 -0
- {pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/test_with_statement.py +0 -0
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
Release Notes
|
|
3
3
|
=============
|
|
4
4
|
|
|
5
|
+
.. _Release Notes_1.1.3:
|
|
6
|
+
|
|
7
|
+
1.1.3
|
|
8
|
+
=====
|
|
9
|
+
|
|
10
|
+
.. _Release Notes_1.1.3_Bug Fixes:
|
|
11
|
+
|
|
12
|
+
Bug Fixes
|
|
13
|
+
---------
|
|
14
|
+
|
|
15
|
+
- Run server threads with daemon flag, preventing shutdown issues.
|
|
16
|
+
`#411 <https://github.com/csernazs/pytest-httpserver/pull/411>`_
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
.. _Release Notes_1.1.2:
|
|
20
|
+
|
|
21
|
+
1.1.2
|
|
22
|
+
=====
|
|
23
|
+
|
|
24
|
+
.. _Release Notes_1.1.2_Deprecation Notes:
|
|
25
|
+
|
|
26
|
+
Deprecation Notes
|
|
27
|
+
-----------------
|
|
28
|
+
|
|
29
|
+
- Python versions earlier than 3.9 have been deprecated in order to make the
|
|
30
|
+
code more type safe. Python 3.8 has reached EOL on 2024-10-07.
|
|
31
|
+
|
|
32
|
+
|
|
5
33
|
.. _Release Notes_1.1.1:
|
|
6
34
|
|
|
7
35
|
1.1.1
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytest_httpserver
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: pytest-httpserver is a httpserver for pytest
|
|
5
5
|
Home-page: https://github.com/csernazs/pytest-httpserver
|
|
6
6
|
License: MIT
|
|
7
7
|
Author: Zsolt Cserna
|
|
8
8
|
Author-email: cserna.zsolt@gmail.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Framework :: Pytest
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pytest_httpserver"
|
|
3
|
-
version = "1.1.
|
|
3
|
+
version = "1.1.3"
|
|
4
4
|
description = "pytest-httpserver is a httpserver for pytest"
|
|
5
5
|
authors = ["Zsolt Cserna <cserna.zsolt@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -24,7 +24,7 @@ include = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[tool.poetry.dependencies]
|
|
27
|
-
python = ">=3.
|
|
27
|
+
python = ">=3.9"
|
|
28
28
|
Werkzeug = ">= 2.0.0"
|
|
29
29
|
|
|
30
30
|
|
|
@@ -38,14 +38,14 @@ pytest_httpserver = "pytest_httpserver.pytest_plugin"
|
|
|
38
38
|
optional = true
|
|
39
39
|
|
|
40
40
|
[tool.poetry.group.develop.dependencies]
|
|
41
|
-
pre-commit = ">=2.20,<
|
|
41
|
+
pre-commit = ">=2.20,<5.0"
|
|
42
42
|
requests = "*"
|
|
43
43
|
Sphinx = ">=5.1.1,<8.0.0"
|
|
44
44
|
sphinx-rtd-theme = ">=1,<4"
|
|
45
45
|
reno = "*"
|
|
46
46
|
types-requests = "*"
|
|
47
47
|
pytest = ">=7.1.3,<9.0.0"
|
|
48
|
-
pytest-cov = ">=3,<
|
|
48
|
+
pytest-cov = ">=3,<7"
|
|
49
49
|
coverage = ">=6.4.4,<8.0.0"
|
|
50
50
|
tomli = { version = "*", markers = "python_version < '3.11'"}
|
|
51
51
|
black = "*"
|
|
@@ -137,5 +137,5 @@ lint.ignore = [
|
|
|
137
137
|
"UP032",
|
|
138
138
|
]
|
|
139
139
|
line-length = 120
|
|
140
|
-
target-version = "
|
|
140
|
+
target-version = "py39"
|
|
141
141
|
exclude = ["doc", "example*.py", "tests/examples/*.py"]
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/pytest_httpserver/blocking_httpserver.py
RENAMED
|
@@ -4,8 +4,6 @@ from queue import Empty
|
|
|
4
4
|
from queue import Queue
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
from typing import Any
|
|
7
|
-
from typing import Mapping
|
|
8
|
-
from typing import Pattern
|
|
9
7
|
|
|
10
8
|
from pytest_httpserver.httpserver import METHOD_ALL
|
|
11
9
|
from pytest_httpserver.httpserver import UNDEFINED
|
|
@@ -16,6 +14,8 @@ from pytest_httpserver.httpserver import RequestHandlerBase
|
|
|
16
14
|
from pytest_httpserver.httpserver import URIPattern
|
|
17
15
|
|
|
18
16
|
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from re import Pattern
|
|
19
19
|
from ssl import SSLContext
|
|
20
20
|
|
|
21
21
|
from werkzeug import Request
|
|
@@ -9,20 +9,19 @@ import threading
|
|
|
9
9
|
import time
|
|
10
10
|
import urllib.parse
|
|
11
11
|
from collections import defaultdict
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from collections.abc import MutableMapping
|
|
12
15
|
from contextlib import contextmanager
|
|
13
16
|
from contextlib import suppress
|
|
14
17
|
from copy import copy
|
|
15
18
|
from enum import Enum
|
|
19
|
+
from re import Pattern
|
|
16
20
|
from typing import TYPE_CHECKING
|
|
17
21
|
from typing import Any
|
|
18
22
|
from typing import Callable
|
|
19
23
|
from typing import ClassVar
|
|
20
|
-
from typing import Iterable
|
|
21
|
-
from typing import Mapping
|
|
22
|
-
from typing import MutableMapping
|
|
23
24
|
from typing import Optional
|
|
24
|
-
from typing import Pattern
|
|
25
|
-
from typing import Tuple
|
|
26
25
|
from typing import Union
|
|
27
26
|
|
|
28
27
|
import werkzeug.http
|
|
@@ -34,13 +33,16 @@ from werkzeug.serving import make_server
|
|
|
34
33
|
|
|
35
34
|
if TYPE_CHECKING:
|
|
36
35
|
from ssl import SSLContext
|
|
36
|
+
from types import TracebackType
|
|
37
|
+
|
|
38
|
+
from werkzeug.serving import BaseWSGIServer
|
|
37
39
|
|
|
38
40
|
URI_DEFAULT = ""
|
|
39
41
|
METHOD_ALL = "__ALL"
|
|
40
42
|
|
|
41
43
|
HEADERS_T = Union[
|
|
42
44
|
Mapping[str, Union[str, Iterable[str]]],
|
|
43
|
-
Iterable[
|
|
45
|
+
Iterable[tuple[str, str]],
|
|
44
46
|
]
|
|
45
47
|
|
|
46
48
|
HVMATCHER_T = Callable[[str, Optional[str], str], bool]
|
|
@@ -113,11 +115,13 @@ class Waiting:
|
|
|
113
115
|
|
|
114
116
|
@property
|
|
115
117
|
def result(self) -> bool:
|
|
116
|
-
return self._result
|
|
118
|
+
return bool(self._result)
|
|
117
119
|
|
|
118
120
|
@property
|
|
119
121
|
def elapsed_time(self) -> float:
|
|
120
122
|
"""Elapsed time in seconds"""
|
|
123
|
+
if self._stop is None:
|
|
124
|
+
raise TypeError("unsupported operand type(s) for -: 'NoneType' and 'float'")
|
|
121
125
|
return self._stop - self._start
|
|
122
126
|
|
|
123
127
|
|
|
@@ -139,7 +143,7 @@ class HeaderValueMatcher:
|
|
|
139
143
|
func = getattr(Authorization, "from_header", None)
|
|
140
144
|
if func is None: # Werkzeug < 2.3.0
|
|
141
145
|
func = werkzeug.http.parse_authorization_header # type: ignore[attr-defined]
|
|
142
|
-
return func(actual) == func(expected)
|
|
146
|
+
return func(actual) == func(expected) # type: ignore
|
|
143
147
|
|
|
144
148
|
@staticmethod
|
|
145
149
|
def default_header_value_matcher(actual: str | None, expected: str) -> bool:
|
|
@@ -174,7 +178,7 @@ class QueryMatcher(abc.ABC):
|
|
|
174
178
|
return values[0] == values[1]
|
|
175
179
|
|
|
176
180
|
@abc.abstractmethod
|
|
177
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
181
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[Any, Any]:
|
|
178
182
|
pass
|
|
179
183
|
|
|
180
184
|
|
|
@@ -195,10 +199,10 @@ class StringQueryMatcher(QueryMatcher):
|
|
|
195
199
|
|
|
196
200
|
self.query_string = query_string
|
|
197
201
|
|
|
198
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
202
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[bytes, bytes]:
|
|
199
203
|
if isinstance(self.query_string, str):
|
|
200
204
|
query_string = self.query_string.encode()
|
|
201
|
-
elif isinstance(self.query_string, bytes):
|
|
205
|
+
elif isinstance(self.query_string, bytes): # type: ignore
|
|
202
206
|
query_string = self.query_string
|
|
203
207
|
else:
|
|
204
208
|
raise TypeError("query_string must be a string, or a bytes-like object")
|
|
@@ -211,7 +215,7 @@ class MappingQueryMatcher(QueryMatcher):
|
|
|
211
215
|
Matches a query string to a dictionary or MultiDict specified
|
|
212
216
|
"""
|
|
213
217
|
|
|
214
|
-
def __init__(self, query_dict: Mapping | MultiDict):
|
|
218
|
+
def __init__(self, query_dict: Mapping[str, str] | MultiDict[str, str]):
|
|
215
219
|
"""
|
|
216
220
|
:param query_dict: if dictionary (Mapping) is specified, it will be used as a
|
|
217
221
|
key-value mapping where both key and value should be string. If there are multiple
|
|
@@ -221,7 +225,7 @@ class MappingQueryMatcher(QueryMatcher):
|
|
|
221
225
|
"""
|
|
222
226
|
self.query_dict = query_dict
|
|
223
227
|
|
|
224
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
228
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[Mapping[str, str], Mapping[str, str]]:
|
|
225
229
|
query = MultiDict(urllib.parse.parse_qsl(request_query_string.decode("utf-8")))
|
|
226
230
|
if isinstance(self.query_dict, MultiDict):
|
|
227
231
|
return (query, self.query_dict)
|
|
@@ -241,14 +245,14 @@ class BooleanQueryMatcher(QueryMatcher):
|
|
|
241
245
|
"""
|
|
242
246
|
self.result = result
|
|
243
247
|
|
|
244
|
-
def get_comparing_values(self, request_query_string): # noqa: ARG002
|
|
248
|
+
def get_comparing_values(self, request_query_string: bytes): # noqa: ARG002
|
|
245
249
|
if self.result:
|
|
246
250
|
return (True, True)
|
|
247
251
|
else:
|
|
248
252
|
return (True, False)
|
|
249
253
|
|
|
250
254
|
|
|
251
|
-
def _create_query_matcher(query_string: None | QueryMatcher | str | bytes | Mapping) -> QueryMatcher:
|
|
255
|
+
def _create_query_matcher(query_string: None | QueryMatcher | str | bytes | Mapping[str, str]) -> QueryMatcher:
|
|
252
256
|
if isinstance(query_string, QueryMatcher):
|
|
253
257
|
return query_string
|
|
254
258
|
|
|
@@ -312,7 +316,7 @@ class RequestMatcher:
|
|
|
312
316
|
data: str | bytes | None = None,
|
|
313
317
|
data_encoding: str = "utf-8",
|
|
314
318
|
headers: Mapping[str, str] | None = None,
|
|
315
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
319
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
316
320
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
317
321
|
json: Any = UNDEFINED,
|
|
318
322
|
):
|
|
@@ -410,7 +414,7 @@ class RequestMatcher:
|
|
|
410
414
|
|
|
411
415
|
return json_received == self.json
|
|
412
416
|
|
|
413
|
-
def difference(self, request: Request) -> list[tuple]:
|
|
417
|
+
def difference(self, request: Request) -> list[tuple[str, str, str | URIPattern]]:
|
|
414
418
|
"""
|
|
415
419
|
Calculates the difference between the matcher and the request.
|
|
416
420
|
|
|
@@ -422,7 +426,7 @@ class RequestMatcher:
|
|
|
422
426
|
matches the fields set in the matcher object.
|
|
423
427
|
"""
|
|
424
428
|
|
|
425
|
-
retval: list[tuple] = []
|
|
429
|
+
retval: list[tuple[str, Any, Any]] = []
|
|
426
430
|
|
|
427
431
|
if not self.match_uri(request):
|
|
428
432
|
retval.append(("uri", request.path, self.uri))
|
|
@@ -433,8 +437,8 @@ class RequestMatcher:
|
|
|
433
437
|
if not self.query_matcher.match(request.query_string):
|
|
434
438
|
retval.append(("query_string", request.query_string, self.query_string))
|
|
435
439
|
|
|
436
|
-
request_headers = {}
|
|
437
|
-
expected_headers = {}
|
|
440
|
+
request_headers: dict[str, str | None] = {}
|
|
441
|
+
expected_headers: dict[str, str] = {}
|
|
438
442
|
for key, value in self.headers.items():
|
|
439
443
|
if not self.header_value_matcher(key, request.headers.get(key), value):
|
|
440
444
|
request_headers[key] = request.headers.get(key)
|
|
@@ -467,7 +471,7 @@ class RequestHandlerBase(abc.ABC):
|
|
|
467
471
|
|
|
468
472
|
def respond_with_json(
|
|
469
473
|
self,
|
|
470
|
-
response_json,
|
|
474
|
+
response_json: Any,
|
|
471
475
|
status: int = 200,
|
|
472
476
|
headers: Mapping[str, str] | None = None,
|
|
473
477
|
content_type: str = "application/json",
|
|
@@ -578,7 +582,7 @@ class RequestHandler(RequestHandlerBase):
|
|
|
578
582
|
return retval
|
|
579
583
|
|
|
580
584
|
|
|
581
|
-
class RequestHandlerList(list):
|
|
585
|
+
class RequestHandlerList(list[RequestHandler]):
|
|
582
586
|
"""
|
|
583
587
|
Represents a list of :py:class:`RequestHandler` objects.
|
|
584
588
|
|
|
@@ -638,9 +642,9 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
638
642
|
"""
|
|
639
643
|
self.host = host
|
|
640
644
|
self.port = port
|
|
641
|
-
self.server = None
|
|
642
|
-
self.server_thread = None
|
|
643
|
-
self.assertions: list[str] = []
|
|
645
|
+
self.server: BaseWSGIServer | None = None
|
|
646
|
+
self.server_thread: threading.Thread | None = None
|
|
647
|
+
self.assertions: list[str | AssertionError] = []
|
|
644
648
|
self.handler_errors: list[Exception] = []
|
|
645
649
|
self.log: list[tuple[Request, Response]] = []
|
|
646
650
|
self.ssl_context = ssl_context
|
|
@@ -727,7 +731,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
727
731
|
|
|
728
732
|
This should not be called directly, but can be overridden to tailor it to your needs.
|
|
729
733
|
"""
|
|
730
|
-
|
|
734
|
+
assert self.server is not None
|
|
731
735
|
self.server.serve_forever()
|
|
732
736
|
|
|
733
737
|
def is_running(self) -> bool:
|
|
@@ -736,7 +740,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
736
740
|
"""
|
|
737
741
|
return bool(self.server)
|
|
738
742
|
|
|
739
|
-
def start(self):
|
|
743
|
+
def start(self) -> None:
|
|
740
744
|
"""
|
|
741
745
|
Start the server in a thread.
|
|
742
746
|
|
|
@@ -755,11 +759,19 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
755
759
|
if self.is_running():
|
|
756
760
|
raise HTTPServerError("Server is already running")
|
|
757
761
|
|
|
762
|
+
app = Request.application(self.application)
|
|
763
|
+
|
|
758
764
|
self.server = make_server(
|
|
759
|
-
self.host,
|
|
765
|
+
self.host,
|
|
766
|
+
self.port,
|
|
767
|
+
app,
|
|
768
|
+
ssl_context=self.ssl_context,
|
|
769
|
+
threaded=self.threaded,
|
|
760
770
|
)
|
|
771
|
+
|
|
761
772
|
self.port = self.server.port # Update port (needed if `port` was set to 0)
|
|
762
|
-
|
|
773
|
+
# Explicitly make the new thread daemonic to avoid shutdown issues
|
|
774
|
+
self.server_thread = threading.Thread(target=self.thread_target, daemon=True)
|
|
763
775
|
self.server_thread.start()
|
|
764
776
|
|
|
765
777
|
def stop(self):
|
|
@@ -772,6 +784,8 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
772
784
|
Only a running server can be stopped. If the sever is not running, :py:class`HTTPServerError`
|
|
773
785
|
will be raised.
|
|
774
786
|
"""
|
|
787
|
+
assert self.server is not None
|
|
788
|
+
assert self.server_thread is not None
|
|
775
789
|
if not self.is_running():
|
|
776
790
|
raise HTTPServerError("Server is not running")
|
|
777
791
|
self.server.shutdown()
|
|
@@ -779,7 +793,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
779
793
|
self.server = None
|
|
780
794
|
self.server_thread = None
|
|
781
795
|
|
|
782
|
-
def add_assertion(self, obj):
|
|
796
|
+
def add_assertion(self, obj: str | AssertionError):
|
|
783
797
|
"""
|
|
784
798
|
Add a new assertion
|
|
785
799
|
|
|
@@ -848,8 +862,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
848
862
|
:return: the response object what the handler responded, or a response which contains the error
|
|
849
863
|
"""
|
|
850
864
|
|
|
851
|
-
|
|
852
|
-
def application(self, request: Request):
|
|
865
|
+
def application(self, request: Request) -> Response:
|
|
853
866
|
"""
|
|
854
867
|
Entry point of werkzeug.
|
|
855
868
|
|
|
@@ -875,7 +888,12 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
875
888
|
self.start()
|
|
876
889
|
return self
|
|
877
890
|
|
|
878
|
-
def __exit__(
|
|
891
|
+
def __exit__(
|
|
892
|
+
self,
|
|
893
|
+
exc_type: type[BaseException] | None,
|
|
894
|
+
exc_value: BaseException | None,
|
|
895
|
+
traceback: TracebackType | None,
|
|
896
|
+
):
|
|
879
897
|
"""
|
|
880
898
|
Provide the context API
|
|
881
899
|
|
|
@@ -886,7 +904,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
886
904
|
self.stop()
|
|
887
905
|
|
|
888
906
|
@staticmethod
|
|
889
|
-
def format_host(host):
|
|
907
|
+
def format_host(host: str):
|
|
890
908
|
"""
|
|
891
909
|
Formats a hostname so it can be used in a URL.
|
|
892
910
|
Notably, this adds brackets around IPV6 addresses when
|
|
@@ -929,8 +947,8 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
929
947
|
|
|
930
948
|
def __init__(
|
|
931
949
|
self,
|
|
932
|
-
host=DEFAULT_LISTEN_HOST,
|
|
933
|
-
port=DEFAULT_LISTEN_PORT,
|
|
950
|
+
host: str = DEFAULT_LISTEN_HOST,
|
|
951
|
+
port: int = DEFAULT_LISTEN_PORT,
|
|
934
952
|
ssl_context: SSLContext | None = None,
|
|
935
953
|
default_waiting_settings: WaitingSettings | None = None,
|
|
936
954
|
*,
|
|
@@ -995,7 +1013,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
995
1013
|
data: str | bytes | None = None,
|
|
996
1014
|
data_encoding: str = "utf-8",
|
|
997
1015
|
headers: Mapping[str, str] | None = None,
|
|
998
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1016
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
999
1017
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
1000
1018
|
handler_type: HandlerType = HandlerType.PERMANENT,
|
|
1001
1019
|
json: Any = UNDEFINED,
|
|
@@ -1078,7 +1096,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1078
1096
|
data: str | bytes | None = None,
|
|
1079
1097
|
data_encoding: str = "utf-8",
|
|
1080
1098
|
headers: Mapping[str, str] | None = None,
|
|
1081
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1099
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
1082
1100
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
1083
1101
|
json: Any = UNDEFINED,
|
|
1084
1102
|
) -> RequestHandler:
|
|
@@ -1133,7 +1151,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1133
1151
|
data: str | bytes | None = None,
|
|
1134
1152
|
data_encoding: str = "utf-8",
|
|
1135
1153
|
headers: Mapping[str, str] | None = None,
|
|
1136
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1154
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
1137
1155
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
1138
1156
|
json: Any = UNDEFINED,
|
|
1139
1157
|
) -> RequestHandler:
|
|
@@ -1191,13 +1209,13 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1191
1209
|
This method is primarily used when reporting errors.
|
|
1192
1210
|
"""
|
|
1193
1211
|
|
|
1194
|
-
def format_handlers(handlers):
|
|
1212
|
+
def format_handlers(handlers: list[RequestHandler]):
|
|
1195
1213
|
if handlers:
|
|
1196
1214
|
return [" {!r}".format(handler.matcher) for handler in handlers]
|
|
1197
1215
|
else:
|
|
1198
1216
|
return [" none"]
|
|
1199
1217
|
|
|
1200
|
-
lines = []
|
|
1218
|
+
lines: list[str] = []
|
|
1201
1219
|
lines.append("Ordered matchers:")
|
|
1202
1220
|
lines.extend(format_handlers(self.ordered_handlers))
|
|
1203
1221
|
lines.append("")
|
|
@@ -8,10 +8,13 @@ import tarfile
|
|
|
8
8
|
import zipfile
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
import pytest
|
|
14
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Iterable
|
|
17
|
+
|
|
15
18
|
try:
|
|
16
19
|
import tomllib
|
|
17
20
|
except ImportError:
|
|
@@ -230,6 +233,7 @@ def test_sdist_contents(build: Build, version: str):
|
|
|
230
233
|
"test_querystring.py",
|
|
231
234
|
"test_release.py",
|
|
232
235
|
"test_ssl.py",
|
|
236
|
+
"test_thread_type.py",
|
|
233
237
|
"test_threaded.py",
|
|
234
238
|
"test_urimatch.py",
|
|
235
239
|
"test_wait.py",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from werkzeug import Response
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from werkzeug import Request
|
|
11
|
+
|
|
12
|
+
from pytest_httpserver import HTTPServer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_server_thread_is_daemon(httpserver: HTTPServer):
|
|
16
|
+
def handler(_request: Request):
|
|
17
|
+
return Response(f"{threading.current_thread().daemon}")
|
|
18
|
+
|
|
19
|
+
httpserver.expect_request("/foo").respond_with_handler(handler)
|
|
20
|
+
|
|
21
|
+
assert requests.get(httpserver.url_for("/foo")).text == "True"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_example_query_params1.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_example_query_params2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_custom_handler.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_custom_hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_json_matcher.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_log_querying.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_query_params_dict.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_timeout_requests.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_url_matcher.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.1 → pytest_httpserver-1.1.3}/tests/examples/test_howto_wait_success.py
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
|