-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from funnel-io/add-assertion-helpers-for-prepa…
…red-requests Add ParsedRequest assertions helper
- Loading branch information
Showing
14 changed files
with
258 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
black | ||
build | ||
flake8 | ||
mypy | ||
pytest | ||
pytest-clarity | ||
pytest-cov | ||
twine | ||
types-requests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,54 @@ | ||
from functools import partial | ||
from requests import Session | ||
from requtests.fake_adapter import FakeAdapter | ||
from requtests.fake_response import fake_response | ||
from .fake_adapter import FakeAdapter | ||
from .fake_response import fake_response | ||
from .protocols import OptionalAssertions, Responder | ||
|
||
|
||
def fake_delete(*responses, assertions=None): | ||
def fake_delete(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "delete") | ||
|
||
|
||
def fake_get(*responses, assertions=None): | ||
def fake_get(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "get") | ||
|
||
|
||
def fake_head(*responses, assertions=None): | ||
def fake_head(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "head") | ||
|
||
|
||
def fake_options(*responses, assertions=None): | ||
def fake_options(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "options") | ||
|
||
|
||
def fake_patch(*responses, assertions=None): | ||
def fake_patch(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "patch") | ||
|
||
|
||
def fake_post(*responses, assertions=None): | ||
def fake_post(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "post") | ||
|
||
|
||
def fake_put(*responses, assertions=None): | ||
def fake_put(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
return partial(fake_request(*responses, assertions=assertions), "put") | ||
|
||
|
||
def fake_request_with_response(assertions=None, **response_config): | ||
def fake_request_with_response( | ||
assertions: OptionalAssertions = None, | ||
**response_config, | ||
) -> Responder: | ||
""" | ||
Creates a request function that returns a response given the response_config. | ||
""" | ||
return fake_request(fake_response(**response_config), assertions=assertions) | ||
|
||
|
||
def fake_request(*responses, assertions=None): | ||
def fake_request(*responses, assertions: OptionalAssertions = None) -> Responder: | ||
""" | ||
Creates a request function that returns the supplied responses, one at a time. | ||
Making a new request after the last response has been returned results in a StopIteration error. | ||
""" | ||
adapter = FakeAdapter(*responses, assertions=assertions) | ||
session = Session() | ||
session.get_adapter = lambda url: adapter | ||
setattr(session, "get_adapter", lambda url: adapter) | ||
return session.request |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import json | ||
from json import JSONDecodeError | ||
from typing import Any, Dict, List, Optional, Union | ||
from urllib.parse import parse_qs, urlparse | ||
|
||
|
||
class CannotParseBodyAsJSON(RuntimeError): | ||
def __init__(self, error): | ||
super().__init__(error) | ||
self.error = error | ||
|
||
|
||
class ParsedRequest: | ||
def __init__(self, prepared_request): | ||
self.prepared_request = prepared_request | ||
self._parsed_url = urlparse(self.prepared_request.url) | ||
self._parsed_query = parse_qs(self._parsed_url.query) | ||
|
||
def __repr__(self): | ||
return f"<{self.__class__.__name__} [{self.method}]>" | ||
|
||
@property | ||
def body(self) -> Optional[bytes]: | ||
return self.prepared_request.body | ||
|
||
@property | ||
def endpoint(self) -> str: | ||
return f"{self._parsed_url.scheme}://{self._parsed_url.netloc}{self._parsed_url.path}" | ||
|
||
@property | ||
def headers(self) -> Dict[str, str]: | ||
return self.prepared_request.headers | ||
|
||
@property | ||
def json(self) -> Any: | ||
""" | ||
The body of the prepared request, parsed as JSON. | ||
Raises a CannotParseBodyAsJSON error if the body is not valid JSON. | ||
""" | ||
try: | ||
return json.loads(self.prepared_request.body) | ||
except (TypeError, JSONDecodeError) as e: | ||
raise CannotParseBodyAsJSON(e) | ||
|
||
@property | ||
def method(self) -> str: | ||
return self.prepared_request.method | ||
|
||
@property | ||
def query(self) -> Dict[str, Any]: | ||
return {key: _delist(value) for key, value in self._parsed_query.items()} | ||
|
||
@property | ||
def text(self) -> str: | ||
""" | ||
The body of the prepared request, decoded as Unicode. | ||
""" | ||
return self.prepared_request.body.decode() if self.prepared_request.body else "" | ||
|
||
@property | ||
def url(self) -> str: | ||
return self.prepared_request.url | ||
|
||
|
||
def _delist(value: List[Any]) -> Union[Any, List[Any]]: | ||
""" | ||
Extracts the value from a list with a single value, leaving other lists unmodifed. | ||
""" | ||
return value[0] if len(value) == 1 else value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from typing import Any, Callable, List, Optional, Protocol, Union | ||
from requests.models import PreparedRequest, Response | ||
|
||
|
||
class AssertionFunction(Protocol): | ||
def __call__(self, prepared_request: PreparedRequest, **kwargs) -> Any: | ||
""" | ||
An assertion function is expected to raise an error if any of its assertions fail. | ||
""" | ||
pass | ||
|
||
|
||
Assertions = Union[AssertionFunction, List[AssertionFunction]] | ||
OptionalAssertions = Optional[Assertions] | ||
Responder = Callable[..., Response] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.