Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-respx for openSUSE:Factory checked in at 2022-10-14 15:41:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-respx (Old) and /work/SRC/openSUSE:Factory/.python-respx.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-respx" Fri Oct 14 15:41:25 2022 rev:3 rq:1010383 version:0.20.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-respx/python-respx.changes 2022-08-22 11:05:12.793712145 +0200 +++ /work/SRC/openSUSE:Factory/.python-respx.new.2275/python-respx.changes 2022-10-14 15:42:08.363878333 +0200 @@ -1,0 +2,17 @@ +Wed Oct 12 15:57:25 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to version 0.20.0 + Changed + * Type Router.__getitem__ to not return optional routes, thanks @flaeppe (#216) + * Change Call.response to raise instead of returning optional response (#217) + * Change CallList.last to raise instead of return optional call (#217) + * Type M() to not return optional pattern, by introducing a Noop pattern (#217) + * Type Route.pattern to not be optional (#217) + Fixed + * Correct type hints for side effects (#217) + Added + * Runs mypy on both tests and respx (#217) + * Added nox test session for python 3.11 (#217) + * Added Call.has_response helper, now that .response raises (#217) + +------------------------------------------------------------------- Old: ---- respx-0.19.2.tar.gz New: ---- respx-0.20.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-respx.spec ++++++ --- /var/tmp/diff_new_pack.ULOgsa/_old 2022-10-14 15:42:08.943879302 +0200 +++ /var/tmp/diff_new_pack.ULOgsa/_new 2022-10-14 15:42:08.947879308 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python3-%{**}} Name: python-respx -Version: 0.19.2 +Version: 0.20.0 Release: 0 Summary: Mock HTTPX with request patterns and response side effects License: BSD-3-Clause ++++++ respx-0.19.2.tar.gz -> respx-0.20.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/CHANGELOG.md new/respx-0.20.0/CHANGELOG.md --- old/respx-0.19.2/CHANGELOG.md 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/CHANGELOG.md 2022-09-16 11:27:02.000000000 +0200 @@ -4,6 +4,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.20.0] - 2022-09-16 + +### Changed +- Type Router.__getitem__ to not return optional routes, thanks @flaeppe (#216) +- Change `Call.response` to raise instead of returning optional response (#217) +- Change `CallList.last` to raise instead of return optional call (#217) +- Type `M()` to not return optional pattern, by introducing a `Noop` pattern (#217) +- Type `Route.pattern` to not be optional (#217) + +### Fixed +- Correct type hints for side effects (#217) + +### Added +- Runs `mypy` on both tests and respx (#217) +- Added nox test session for python 3.11 (#217) +- Added `Call.has_response` helper, now that `.response` raises (#217) + +## [0.19.3] - 2022-09-14 + +### Fixed +- Fix typing for Route modulos arg +- Respect patterns with empty value when using equal lookup (#206) +- Use pytest asyncio auto mode (#212) +- Fix mock decorator to work together with pytest fixtures (#213) +- Wrap pytest function correctly, i.e. don't hide real function name (#213) + +### Changed +- Enable mypy strict_optional (#201) + + ## [0.19.2] - 2022-02-03 ### Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/docs/stylesheets/slate.css new/respx-0.20.0/docs/stylesheets/slate.css --- old/respx-0.19.2/docs/stylesheets/slate.css 1970-01-01 01:00:00.000000000 +0100 +++ new/respx-0.20.0/docs/stylesheets/slate.css 2022-09-16 11:27:02.000000000 +0200 @@ -0,0 +1,7 @@ +[data-md-color-scheme="slate"] { + --md-hue: 245; + --md-typeset-a-color: #9772d7; + --md-default-bg-color: hsla(var(--md-hue),15%,11%,1); + --md-footer-bg-color: hsla(var(--md-hue),15%,5%,0.87); + --md-footer-bg-color--dark: hsla(var(--md-hue),15%,1%,1); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/mkdocs.yml new/respx-0.20.0/mkdocs.yml --- old/respx-0.19.2/mkdocs.yml 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/mkdocs.yml 2022-09-16 11:27:02.000000000 +0200 @@ -4,11 +4,26 @@ theme: name: "material" - palette: - primary: "deep purple" - accent: "deep purple" icon: logo: "material/school" + palette: + - scheme: default + media: "(prefers-color-scheme: light)" + primary: "deep purple" + accent: "deep purple" + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + media: "(prefers-color-scheme: dark)" + primary: "deep purple" + accent: "deep purple" + toggle: + icon: material/weather-sunny + name: Switch to light mode + +extra_css: + - stylesheets/slate.css repo_name: lundberg/respx repo_url: https://github.com/lundberg/respx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/noxfile.py new/respx-0.20.0/noxfile.py --- old/respx-0.19.2/noxfile.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/noxfile.py 2022-09-16 11:27:02.000000000 +0200 @@ -9,7 +9,7 @@ docs_requirements = ("mkdocs", "mkdocs-material", "mkautodoc>=0.1.0") -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]) def test(session): deps = ["pytest", "pytest-asyncio", "pytest-cov", "trio", "starlette", "flask"] session.install("--upgrade", *deps) @@ -22,7 +22,7 @@ session.run("pytest", "-v", *options) -@nox.session +@nox.session(python="3.6") def check(session): session.install("--upgrade", "flake8-bugbear", "mypy", *lint_requirements) session.install("-e", ".") @@ -30,7 +30,7 @@ session.run("black", "--check", "--diff", "--target-version=py36", *source_files) session.run("isort", "--check", "--diff", "--project=respx", *source_files) session.run("flake8", *source_files) - session.run("mypy", "respx") + session.run("mypy") @nox.session diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/__version__.py new/respx-0.20.0/respx/__version__.py --- old/respx-0.19.2/respx/__version__.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/__version__.py 2022-09-16 11:27:02.000000000 +0200 @@ -1 +1 @@ -__version__ = "0.19.2" +__version__ = "0.20.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/handlers.py new/respx-0.20.0/respx/handlers.py --- old/respx-0.19.2/respx/handlers.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/handlers.py 2022-09-16 11:27:02.000000000 +0200 @@ -8,7 +8,10 @@ self.transport = transport def __call__(self, request: httpx.Request) -> httpx.Response: - if not isinstance(request.stream, httpx.SyncByteStream): # pragma: nocover + if not isinstance( + request.stream, # type: ignore[has-type] + httpx.SyncByteStream, + ): # pragma: nocover raise RuntimeError("Attempted to route an async request to a sync app.") return self.transport.handle_request(request) @@ -19,7 +22,10 @@ self.transport = transport async def __call__(self, request: httpx.Request) -> httpx.Response: - if not isinstance(request.stream, httpx.AsyncByteStream): # pragma: nocover + if not isinstance( + request.stream, # type: ignore[has-type] + httpx.AsyncByteStream, + ): # pragma: nocover raise RuntimeError("Attempted to route a sync request to an async app.") return await self.transport.handle_async_request(request) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/models.py new/respx-0.20.0/respx/models.py --- old/respx-0.19.2/respx/models.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/models.py 2022-09-16 11:27:02.000000000 +0200 @@ -1,16 +1,15 @@ import inspect from typing import ( Any, - Callable, Dict, Iterator, List, NamedTuple, Optional, + Sequence, Tuple, Type, Union, - cast, ) from unittest import mock from warnings import warn @@ -24,6 +23,7 @@ HeaderTypes, ResolvedResponseTypes, RouteResultTypes, + SideEffectListTypes, SideEffectTypes, ) @@ -35,7 +35,7 @@ response = httpx.Response( response.status_code, headers=response.headers, - stream=response.stream, + stream=response.stream, # type: ignore[has-type] request=request, extensions=dict(response.extensions), ) @@ -44,30 +44,40 @@ class Call(NamedTuple): request: httpx.Request - response: Optional[httpx.Response] + optional_response: Optional[httpx.Response] + + @property + def response(self) -> httpx.Response: + if self.optional_response is None: + raise ValueError(f"{self!r} has no response") + return self.optional_response + + @property + def has_response(self) -> bool: + return self.optional_response is not None class CallList(list, mock.NonCallableMock): - def __init__(self, *args, name="respx", **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, *args: Sequence[Call], name: Any = "respx") -> None: + super().__init__(*args) mock.NonCallableMock.__init__(self, name=name) @property - def called(self) -> bool: # type: ignore + def called(self) -> bool: # type: ignore[override] return bool(self) @property - def call_count(self) -> int: # type: ignore + def call_count(self) -> int: # type: ignore[override] return len(self) @property - def last(self) -> Optional[Call]: - return self[-1] if self else None + def last(self) -> Call: + return self[-1] def record( self, request: httpx.Request, response: Optional[httpx.Response] ) -> Call: - call = Call(request=request, response=response) + call = Call(request=request, optional_response=response) self.append(call) return call @@ -90,7 +100,9 @@ f"got {content!r}. Please use json=... or side effects." ) - super().__init__(status_code or 200, content=content, **kwargs) + if content is not None: + kwargs["content"] = content + super().__init__(status_code or 200, **kwargs) if content_type: self.headers["Content-Type"] = content_type @@ -126,7 +138,7 @@ self.side_effect = side_effect return side_effect - def __mod__(self, response: Union[int, Dict[str, Any]]) -> "Route": + def __mod__(self, response: Union[int, Dict[str, Any], httpx.Response]) -> "Route": if isinstance(response, int): self.return_value = httpx.Response(status_code=response) @@ -153,7 +165,7 @@ raise NotImplementedError("Can't set name on route.") @property - def pattern(self) -> Optional[Pattern]: + def pattern(self) -> Pattern: return self._pattern @pattern.setter @@ -172,15 +184,20 @@ self._return_value = return_value @property - def side_effect(self) -> Optional[SideEffectTypes]: + def side_effect( + self, + ) -> Optional[Union[SideEffectTypes, Sequence[SideEffectListTypes]]]: return self._side_effect @side_effect.setter - def side_effect(self, side_effect: Optional[SideEffectTypes]) -> None: + def side_effect( + self, + side_effect: Optional[Union[SideEffectTypes, Sequence[SideEffectListTypes]]], + ) -> None: self.pass_through(False) if not side_effect: self._side_effect = None - elif isinstance(side_effect, (tuple, list, Iterator)): + elif isinstance(side_effect, (Iterator, Sequence)): self._side_effect = iter(side_effect) else: self._side_effect = side_effect @@ -225,7 +242,9 @@ self, return_value: Optional[httpx.Response] = None, *, - side_effect: Optional[SideEffectTypes] = None, + side_effect: Optional[ + Union[SideEffectTypes, Sequence[SideEffectListTypes]] + ] = None, ) -> "Route": self.return_value = return_value self.side_effect = side_effect @@ -277,14 +296,13 @@ def _next_side_effect( self, - ) -> Union[Callable, Exception, Type[Exception], httpx.Response]: - effect: Union[Callable, Exception, Type[Exception], httpx.Response] + ) -> Union[CallableSideEffect, Exception, Type[Exception], httpx.Response]: + assert self._side_effect is not None + effect: Union[CallableSideEffect, Exception, Type[Exception], httpx.Response] if isinstance(self._side_effect, Iterator): effect = next(self._side_effect) else: - effect = cast( - Union[Callable, Exception, Type[Exception]], self._side_effect - ) + effect = self._side_effect return effect @@ -328,19 +346,18 @@ raise SideEffectError(self, origin=effect) # Handle Exception `type` side effect - Error: Type[Exception] = cast(Type[Exception], effect) - if isinstance(effect, type) and issubclass(Error, Exception): + elif isinstance(effect, type) and issubclass(effect, Exception): raise SideEffectError( self, origin=( - Error("Mock Error", request=request) - if issubclass(Error, httpx.RequestError) - else Error() + effect("Mock Error", request=request) + if issubclass(effect, httpx.RequestError) + else effect() ), ) # Handle `Callable` side effect - if callable(effect): + elif callable(effect): result = self._call_side_effect(effect, request, **kwargs) return result @@ -375,10 +392,10 @@ Returns None for a non-matching route, mocked response for a match, or input request for pass-through. """ - context = {} + context: Dict[str, Any] = {} if self._pattern: - match = self.pattern.match(request) + match = self._pattern.match(request) if not match: return None context = match.context @@ -417,7 +434,7 @@ def __contains__(self, name: str) -> bool: return name in self._names - def __getitem__(self, key: Union[int, str]) -> Optional[Route]: + def __getitem__(self, key: Union[int, str]) -> Route: if isinstance(key, int): return self._routes[key] else: @@ -427,6 +444,8 @@ """ Re-set all routes to given routes. """ + if (i.start, i.stop, i.step) != (None, None, None): + raise TypeError("Can't slice assign routes") self._routes = list(routes._routes) self._names = dict(routes._names) @@ -436,7 +455,7 @@ def add(self, route: Route, name: Optional[str] = None) -> Route: # Find route with same name - existing_route = self._names.pop(name, None) + existing_route = self._names.pop(name or "", None) if route in self._routes: if existing_route and existing_route != route: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/patterns.py new/respx-0.20.0/respx/patterns.py --- old/respx-0.19.2/respx/patterns.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/patterns.py 2022-09-16 11:27:02.000000000 +0200 @@ -88,13 +88,26 @@ def __iter__(self): yield self + def __bool__(self): + return True + def __and__(self, other: "Pattern") -> "Pattern": + if not bool(other): + return self + elif not bool(self): + return other return _And((self, other)) def __or__(self, other: "Pattern") -> "Pattern": + if not bool(other): + return self + elif not bool(self): + return other return _Or((self, other)) def __invert__(self): + if not bool(self): + return self return _Invert(self) def __repr__(self): # pragma: nocover @@ -159,6 +172,22 @@ return Match(value in self.value) +class Noop(Pattern): + def __init__(self) -> None: + super().__init__(None) + + def __repr__(self): + return f"<{self.__class__.__name__}>" + + def __bool__(self) -> bool: + # Treat this pattern as non-existent, e.g. when filtering or conditioning + return False + + def match(self, request: httpx.Request) -> Match: + # If this pattern is part of a combined pattern, always be truthy, i.e. noop + return Match(True) + + class PathPattern(Pattern): path: Optional[str] @@ -388,8 +417,9 @@ return request.url.path def strip_base(self, value: str) -> str: - value = value[len(self.base.value) :] - value = "/" + value if not value.startswith("/") else value + if self.base: + value = value[len(self.base.value) :] + value = "/" + value if not value.startswith("/") else value return value @@ -503,9 +533,6 @@ extras = None for pattern__lookup, value in lookups.items(): - if not value: - continue - # Handle url pattern if pattern__lookup == "url": extras = parse_url_patterns(value) @@ -534,6 +561,10 @@ lookup = Lookup(lookup_name) if lookup_name else None pattern = P(value, lookup=lookup) + # Skip patterns with no value, exept when using equal lookup + if not pattern.value and pattern.lookup is not Lookup.EQUAL: + continue + patterns += (pattern,) # Combine and merge patterns @@ -545,15 +576,13 @@ def get_scheme_port(scheme: Optional[str]) -> Optional[int]: - return {"http": 80, "https": 443}.get(scheme) + return {"http": 80, "https": 443}.get(scheme or "") -def combine( - patterns: Sequence[Pattern], op: Callable = operator.and_ -) -> Optional[Pattern]: +def combine(patterns: Sequence[Pattern], op: Callable = operator.and_) -> Pattern: patterns = tuple(filter(None, patterns)) if not patterns: - return None + return Noop() return reduce(op, patterns) @@ -600,13 +629,13 @@ if not bases: return pattern - if pattern: - # Flatten pattern - patterns = list(iter(pattern)) + # Flatten pattern + patterns: List[Pattern] = list(filter(None, iter(pattern))) + if patterns: if "host" in (_pattern.key for _pattern in patterns): # Pattern is "absolute", skip merging - bases = None + bases = {} else: # Traverse pattern and set related base for _pattern in patterns: @@ -619,7 +648,7 @@ if bases: # Combine left over base patterns with pattern base_pattern = combine(list(bases.values())) - if pattern: + if pattern and base_pattern: pattern = base_pattern & pattern else: pattern = base_pattern diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/router.py new/respx-0.20.0/respx/router.py --- old/respx-0.19.2/respx/router.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/router.py 2022-09-16 11:27:02.000000000 +0200 @@ -1,6 +1,6 @@ import inspect from contextlib import contextmanager -from functools import update_wrapper +from functools import partial, update_wrapper, wraps from types import TracebackType from typing import ( Any, @@ -283,7 +283,9 @@ resolved.response = cast(ResolvedResponseTypes, prospect) break - if isinstance(resolved.response.stream, httpx.ByteStream): + if resolved.response and isinstance( + resolved.response.stream, httpx.ByteStream # type: ignore[has-type] + ): resolved.response.read() # Pre-read stream return resolved @@ -305,7 +307,9 @@ resolved.response = cast(ResolvedResponseTypes, prospect) break - if isinstance(resolved.response.stream, httpx.ByteStream): + if resolved.response and isinstance( + resolved.response.stream, httpx.ByteStream # type: ignore[has-type] + ): await resolved.response.aread() # Pre-read stream return resolved @@ -322,8 +326,6 @@ class MockRouter(Router): - Mocker: Optional[Type[Mocker]] - def __init__( self, *, @@ -337,6 +339,7 @@ assert_all_mocked=assert_all_mocked, base_url=base_url, ) + self.Mocker: Optional[Type[Mocker]] = None self._using = using @overload @@ -395,33 +398,36 @@ return respx_mock # Determine if decorated function needs a `respx_mock` instance + is_async = inspect.iscoroutinefunction(func) argspec = inspect.getfullargspec(func) needs_mock_reference = "respx_mock" in argspec.args + if needs_mock_reference: + func = partial(func, respx_mock=self) + # Async Decorator - async def async_decorator(*args, **kwargs): + async def _async_decorator(*args, **kwargs): assert func is not None - if needs_mock_reference: - kwargs["respx_mock"] = self async with self: return await func(*args, **kwargs) # Sync Decorator - def sync_decorator(*args, **kwargs): + def _sync_decorator(*args, **kwargs): assert func is not None - if "respx_mock" in argspec.args: - kwargs["respx_mock"] = self with self: return func(*args, **kwargs) - if not needs_mock_reference: - async_decorator = update_wrapper(async_decorator, func) - sync_decorator = update_wrapper(sync_decorator, func) + if needs_mock_reference: + async_decorator = wraps(func)(_async_decorator) + sync_decorator = wraps(func)(_sync_decorator) + else: + async_decorator = update_wrapper(_async_decorator, func) + sync_decorator = update_wrapper(_sync_decorator, func) # Dispatch async/sync decorator, depending on decorated function. # - Only stage when using global decorator `@respx.mock` # - Second stage when using local decorator `@respx.mock(...)` - return async_decorator if inspect.iscoroutinefunction(func) else sync_decorator + return async_decorator if is_async else sync_decorator def __enter__(self) -> "MockRouter": self.start() @@ -429,9 +435,9 @@ def __exit__( self, - exc_type: Type[BaseException] = None, - exc_value: BaseException = None, - traceback: TracebackType = None, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + traceback: Optional[TracebackType] = None, ) -> None: self.stop(quiet=bool(exc_type is not None)) @@ -461,7 +467,7 @@ Register transport, snapshot router and start patching. """ self.snapshot() - self.Mocker = Mocker.registry.get(self.using) + self.Mocker = Mocker.registry.get(self.using or "") if self.Mocker: self.Mocker.register(self) self.Mocker.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/transports.py new/respx-0.20.0/respx/transports.py --- old/respx-0.19.2/respx/transports.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/transports.py 2022-09-16 11:27:02.000000000 +0200 @@ -55,9 +55,9 @@ def __exit__( self, - exc_type: Type[BaseException] = None, - exc_value: BaseException = None, - traceback: TracebackType = None, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + traceback: Optional[TracebackType] = None, ) -> None: if not exc_type and self._router and self._router._assert_all_called: self._router.assert_all_called() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/respx/types.py new/respx-0.20.0/respx/types.py --- old/respx-0.19.2/respx/types.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/respx/types.py 2022-09-16 11:27:02.000000000 +0200 @@ -51,6 +51,5 @@ CallableSideEffect, Exception, Type[Exception], - Sequence[SideEffectListTypes], Iterator[SideEffectListTypes], ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/setup.cfg new/respx-0.20.0/setup.cfg --- old/respx-0.19.2/setup.cfg 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/setup.cfg 2022-09-16 11:27:02.000000000 +0200 @@ -3,7 +3,7 @@ [flake8] max-line-length = 88 -ignore = E501,E266,E731,W503,E203 +ignore = E501,E266,E731,W503,E203,B024 exclude = .git show-source = true @@ -25,6 +25,7 @@ --cov-report=xml --cov-fail-under 100 -rxXs +asyncio_mode = auto [coverage:run] source = respx,tests @@ -35,10 +36,14 @@ show_missing = True [mypy] +python_version = 3.6 +files = respx,tests +pretty = True + no_implicit_reexport = True no_implicit_optional = True strict_equality = True -strict_optional = False +strict_optional = True check_untyped_defs = True disallow_incomplete_defs = True ignore_missing_imports = False @@ -48,5 +53,16 @@ warn_unused_ignores = True warn_unreachable = True +show_error_codes = True + [mypy-pytest.*] ignore_missing_imports = True + +[mypy-trio.*] +ignore_missing_imports = True + +[mypy-flask.*] +ignore_missing_imports = True + +[mypy-starlette.*] +ignore_missing_imports = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_api.py new/respx-0.20.0/tests/test_api.py --- old/respx-0.19.2/tests/test_api.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_api.py 2022-09-16 11:27:02.000000000 +0200 @@ -99,7 +99,7 @@ async def test_invalid_url_pattern(): async with MockRouter() as respx_mock: with pytest.raises(TypeError): - respx_mock.get(["invalid"]) + respx_mock.get(["invalid"]) # type: ignore[arg-type] @pytest.mark.asyncio @@ -277,7 +277,10 @@ async with MockRouter() as respx_mock: url = "https://foo.bar/" request = respx_mock.get(url) - request.side_effect = httpx.ConnectTimeout("X-P", request=None) + request.side_effect = httpx.ConnectTimeout( + "X-P", + request=None, # type: ignore[arg-type] + ) with pytest.raises(httpx.ConnectTimeout): await client.get(url) @@ -293,7 +296,9 @@ assert route.call_count == 2 assert route.calls.last.request is not None - assert route.calls.last.response is None + assert route.calls.last.has_response is False + with pytest.raises(ValueError, match="has no response"): + assert route.calls.last.response @pytest.mark.asyncio @@ -303,7 +308,7 @@ def content_callback(request, slug): content = jsonlib.loads(request.content) - return respx.MockResponse(text=f"hello {slug}{content['x']}") + return respx.MockResponse(content=f"hello {slug}{content['x']}") request = respx_mock.post(url_pattern) request.side_effect = content_callback @@ -356,7 +361,9 @@ assert response.text == "hello lundberg" with pytest.raises(TypeError): - respx_mock.get("https://ham.spam/").mock(side_effect=lambda req: "invalid") + respx_mock.get("https://ham.spam/").mock( + side_effect=lambda req: "invalid" # type: ignore[arg-type,return-value] + ) await client.get("https://ham.spam/") with pytest.raises(httpx.NetworkError): @@ -526,10 +533,10 @@ assert respx.routes["foobar"].called with pytest.raises(TypeError): - respx.add(route, status_code=418) # pragma: nocover + respx.add(route, status_code=418) # type: ignore[call-arg] with pytest.raises(ValueError): - respx.add("GET") # pragma: nocover + respx.add("GET") # type: ignore[arg-type] with pytest.raises(NotImplementedError): route.name = "spam" @@ -554,7 +561,7 @@ route.respond(content={}) with pytest.raises(TypeError, match="content can only be"): - route.respond(content=Exception()) + route.respond(content=Exception()) # type: ignore[arg-type] @pytest.mark.asyncio diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_mock.py new/respx-0.20.0/tests/test_mock.py --- old/respx-0.19.2/tests/test_mock.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_mock.py 2022-09-16 11:27:02.000000000 +0200 @@ -299,6 +299,14 @@ @pytest.mark.asyncio +@respx.mock(base_url="https://foo.bar") +async def test_configured_decorator_with_fixture(respx_mock, client): + respx_mock.get("/") + response = await client.get("https://foo.bar/") + assert response.status_code == 200 + + +@pytest.mark.asyncio async def test_configured_router_reuse(client): router = respx.mock() route = router.get("https://foo/bar/") % 404 @@ -309,25 +317,25 @@ with router: route.return_value = httpx.Response(202) response = await client.get("https://foo/bar/") - assert route.called is True + assert route.called == True # noqa: E712 assert response.status_code == 202 assert router.calls.call_count == 1 assert respx.calls.call_count == 0 assert len(router.routes) == 1 - assert route.called is False + assert route.called == False # noqa: E712 assert router.calls.call_count == 0 async with router: assert router.calls.call_count == 0 response = await client.get("https://foo/bar/") - assert route.called is True + assert route.called == True # noqa: E712 assert response.status_code == 404 assert router.calls.call_count == 1 assert respx.calls.call_count == 0 assert len(router.routes) == 1 - assert route.called is False + assert route.called == False # noqa: E712 assert router.calls.call_count == 0 assert respx.calls.call_count == 0 @@ -338,7 +346,7 @@ route = router.get("https://hot.dog/") with pytest.raises(TypeError): - route.return_value = "not-a-httpx-response" + route.return_value = "not-a-httpx-response" # type: ignore[assignment] @pytest.mark.asyncio @@ -388,7 +396,7 @@ try: respx.start() response = await client.get(url) - assert request.called is True + assert request.called == True # noqa: E712 assert response.status_code == 202 assert response.text == "" assert respx.calls.call_count == 1 @@ -396,12 +404,12 @@ respx.stop(clear=False, reset=False) assert len(respx.routes) == 1 assert respx.calls.call_count == 1 - assert request.called is True + assert request.called == True # noqa: E712 respx.reset() assert len(respx.routes) == 1 assert respx.calls.call_count == 0 - assert request.called is False + assert request.called == False # noqa: E712 respx.clear() assert len(respx.routes) == 0 @@ -537,7 +545,7 @@ def test_router_using__invalid(): with pytest.raises(ValueError, match="using"): - respx.MockRouter(using=123).using + respx.MockRouter(using=123).using # type: ignore[arg-type] def test_mocker_subclass(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_patterns.py new/respx-0.20.0/tests/test_patterns.py --- old/respx-0.19.2/tests/test_patterns.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_patterns.py 2022-09-16 11:27:02.000000000 +0200 @@ -15,6 +15,7 @@ Lookup, M, Method, + Noop, Params, Path, Pattern, @@ -66,6 +67,31 @@ assert match.context == {"host": "foo.bar", "slug": "baz"} +def test_noop_pattern(): + assert bool(Noop()) is False + assert bool(Noop().match(httpx.Request("GET", "https://example.org"))) is True + assert list(filter(None, [Noop()])) == [] + assert repr(Noop()) == "<Noop>" + assert isinstance(~Noop(), Noop) + assert Method("GET") & Noop() == Method("GET") + assert Noop() & Method("GET") == Method("GET") + assert Method("GET") | Noop() == Method("GET") + assert Noop() | Method("GET") == Method("GET") + + +@pytest.mark.parametrize( + "kwargs,url,expected", + [ + ({"params__eq": {}}, "https://foo.bar/", True), + ({"params__eq": {}}, "https://foo.bar/?x=y", False), + ({"params__contains": {}}, "https://foo.bar/?x=y", True), + ], +) +def test_m_pattern(kwargs, url, expected): + request = httpx.Request("GET", url) + assert bool(M(host="foo.bar", **kwargs).match(request)) is expected + + @pytest.mark.parametrize( "lookup,value,expected", [ @@ -191,6 +217,11 @@ request = httpx.Request("GET", "https://foo.bar/baz/") assert Path(["/egg/", "/baz/"], lookup=Lookup.IN).match(request) + path = Path("/bar/") + assert path.strip_base("/foo/bar/") == "/foo/bar/" + path.base = Path("/foo/") + assert path.strip_base("/foo/bar/") == "/bar/" + @pytest.mark.parametrize( "lookup,params,url,expected", @@ -212,6 +243,8 @@ (Lookup.EQUAL, "y=2", "https://foo.bar/?x=1", False), (Lookup.EQUAL, {"x": ANY}, "https://foo.bar/?x=1", True), (Lookup.EQUAL, {"y": ANY}, "https://foo.bar/?x=1", False), + (Lookup.EQUAL, {}, "https://foo.bar/?x=1", False), + (Lookup.EQUAL, {}, "https://foo.bar/", True), (Lookup.EQUAL, "x=1&y=2", "https://foo.bar/?x=1", False), (Lookup.EQUAL, "y=2&x=1", "https://foo.bar/?x=1&y=2", True), (Lookup.EQUAL, "y=3&x=2&x=1", "https://foo.bar/?x=1&x=2&y=3", False), # ordered diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_plugin.py new/respx-0.20.0/tests/test_plugin.py --- old/respx-0.19.2/tests/test_plugin.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_plugin.py 2022-09-16 11:27:02.000000000 +0200 @@ -4,6 +4,9 @@ import httpx import pytest + @pytest.fixture + def some_fixture(): + yield "foobar" def test_plain_fixture(respx_mock): route = respx_mock.get("https://foo.bar/") % 204 @@ -18,7 +21,20 @@ assert response.status_code == 204 response = httpx.get("https://example.org/") assert response.status_code == 200 + + + def test_with_extra_fixture(respx_mock, some_fixture): + import respx + assert isinstance(respx_mock, respx.Router) + assert some_fixture == "foobar" + + + @pytest.mark.respx(assert_all_mocked=False) + def test_marked_with_extra_fixture(respx_mock, some_fixture): + import respx + assert isinstance(respx_mock, respx.Router) + assert some_fixture == "foobar" """ ) result = testdir.runpytest("-p", "respx") - result.assert_outcomes(passed=2) + result.assert_outcomes(passed=4) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_remote.py new/respx-0.20.0/tests/test_remote.py --- old/respx-0.19.2/tests/test_remote.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_remote.py 2022-09-16 11:27:02.000000000 +0200 @@ -37,7 +37,7 @@ assert response.json()["json"] == {"foo": "bar"} assert respx_mock.calls.last.request.url == url - assert respx_mock.calls.last.response is None + assert respx_mock.calls.last.has_response is False assert route.call_count == call_count assert respx_mock.calls.call_count == call_count diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_router.py new/respx-0.20.0/tests/test_router.py --- old/respx-0.19.2/tests/test_router.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_router.py 2022-09-16 11:27:02.000000000 +0200 @@ -29,11 +29,13 @@ resolved = router.resolve(request) assert resolved.route is None + assert isinstance(resolved.response, httpx.Response) assert resolved.response.status_code == 200 resolved = await router.aresolve(request) assert resolved.route is None + assert isinstance(resolved.response, httpx.Response) assert resolved.response.status_code == 200 @@ -62,6 +64,7 @@ resolved = router.resolve(request) assert bool(resolved.route is route) is expected + assert isinstance(resolved.response, httpx.Response) if expected: assert bool(resolved.response.status_code == 201) is expected else: @@ -82,6 +85,7 @@ route.pass_through(False) resolved = router.resolve(request) + assert resolved.route is not None assert resolved.route is route assert not resolved.route.is_pass_through assert resolved.response is not None @@ -106,6 +110,7 @@ resolved = router.resolve(request) assert bool(resolved.route is route) is expected + assert isinstance(resolved.response, httpx.Response) if expected: assert bool(resolved.response.status_code == 201) is expected else: @@ -151,23 +156,26 @@ request = httpx.Request("GET", "https://foo.bar/baz/") resolved = router.resolve(request) + assert isinstance(resolved.response, httpx.Response) assert resolved.response.status_code == 404 assert resolved.route is route1b assert route1a is route1b request = httpx.Request("GET", "https://foo.bar/") resolved = router.resolve(request) + assert isinstance(resolved.response, httpx.Response) assert resolved.response.status_code == 201 assert resolved.route is route2 request = httpx.Request("POST", "https://fox.zoo/") resolved = router.resolve(request) + assert isinstance(resolved.response, httpx.Response) assert resolved.response.status_code == 401 assert resolved.response.json() == {"error": "x"} assert resolved.route is route3 with pytest.raises(TypeError, match="Route can only"): - router.route() % [] + router.route() % [] # type: ignore[operator] @pytest.mark.asyncio @@ -197,7 +205,7 @@ request = httpx.Request("GET", "https://foo.bar/baz/") response = router.handler(request) assert response.status_code == 204 - assert response.request.respx_was_here is True + assert response.request.respx_was_here is True # type: ignore[attr-defined] def test_side_effect_with_route_kwarg(): @@ -379,12 +387,12 @@ assert len(router.routes) == 1 assert router.calls.call_count == 0 - assert route.return_value is None + assert route.return_value == None # noqa: E711 router.rollback() # Empty initial state assert len(router.routes) == 0 - assert route.return_value is None + assert route.return_value == None # noqa: E711 # Idempotent route.rollback() @@ -523,3 +531,9 @@ routes.add(foobar2, name="foobar") assert list(routes) == [foobar2] assert routes["foobar"] is foobar1 + + +def test_routelist__unable_to_slice_assign(): + routes = RouteList() + with pytest.raises(TypeError, match="slice assign"): + routes[0:1] = routes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/respx-0.19.2/tests/test_stats.py new/respx-0.20.0/tests/test_stats.py --- old/respx-0.19.2/tests/test_stats.py 2022-02-03 11:16:49.000000000 +0100 +++ new/respx-0.20.0/tests/test_stats.py 2022-09-16 11:27:02.000000000 +0200 @@ -18,7 +18,7 @@ @respx.mock -async def backend_test(backend): +async def backend_test(): url = "https://foo.bar/1/" respx.get(re.compile("https://some.thing")) respx.delete("https://some.thing") @@ -26,10 +26,11 @@ foobar1 = respx.get(url, name="get_foobar") % dict(status_code=202, text="get") foobar2 = respx.delete(url, name="del_foobar") % dict(text="del") - assert foobar1.called is False + assert foobar1.called == False # noqa: E712 assert foobar1.call_count == len(foobar1.calls) assert foobar1.call_count == 0 - assert foobar1.calls.last is None + with pytest.raises(IndexError): + foobar1.calls.last assert respx.calls.call_count == len(respx.calls) assert respx.calls.call_count == 0 @@ -43,8 +44,8 @@ get_response = await client.get(url) del_response = await client.delete(url) - assert foobar1.called is True - assert foobar2.called is True + assert foobar1.called == True # noqa: E712 + assert foobar2.called == True # noqa: E712 assert foobar1.call_count == 1 assert foobar2.call_count == 1 assert foobar1.calls.call_count == 1 @@ -92,19 +93,14 @@ def test_asyncio(): import asyncio - from httpcore.backends.asyncio import AsyncIOBackend - - backend = AsyncIOBackend() # TODO: Why instantiate a backend? loop = asyncio.new_event_loop() try: - loop.run_until_complete(backend_test(backend)) + loop.run_until_complete(backend_test()) finally: loop.close() def test_trio(): # pragma: nocover import trio - from httpcore.backends.trio import TrioBackend - backend = TrioBackend() # TODO: Why instantiate a backend? - trio.run(backend_test, backend) + trio.run(backend_test)