From ae49747fa7b9741194935fcbe43c974b327a91bc Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Mon, 12 Feb 2024 16:18:01 -0600 Subject: [PATCH 1/8] #6: add indexing to BodyOfTheLastRequest. --- screenpy_requests/actions/__init__.py | 4 +-- .../questions/body_of_the_last_response.py | 26 ++++++++++++++++++- tests/conftest.py | 1 - tests/test_actions.py | 3 ++- tests/test_questions.py | 17 +++++++++++- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/screenpy_requests/actions/__init__.py b/screenpy_requests/actions/__init__.py index 5f7c6c0..acb2c73 100644 --- a/screenpy_requests/actions/__init__.py +++ b/screenpy_requests/actions/__init__.py @@ -23,11 +23,11 @@ def generate_send_method_class(method: str) -> Type[APIMethodAction]: """ class SendMETHODRequest: - "Will be programmatically replaced." + """Will be programmatically replaced.""" @staticmethod def to(url: str) -> SendAPIRequest: - "Will be programmatically replaced." + """Will be programmatically replaced.""" return SendAPIRequest(method, url) SendMETHODRequest.__doc__ = f"""Send a {method} request to a URL. diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index b561b29..3027c22 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -2,6 +2,8 @@ Investigate the body of the last API response received by the Actor. """ +from __future__ import annotations + from json.decoder import JSONDecodeError from typing import Union @@ -15,6 +17,9 @@ class BodyOfTheLastResponse: """Ask about the body of the last API response received by the Actor. + If you are expecting a JSON response, this Question can be indexed to + return only a section of that response. See the examples below. + Abilities Required: :class:`~screenpy_requests.abilities.MakeAPIRequests` @@ -24,11 +29,27 @@ class BodyOfTheLastResponse: See.the(BodyOfTheLastResponse(), ContainsTheEntry(play="Hamlet")) ) + the_actor.should( + See.the( + BodyOfTheLastResponse()["users"][0]["first_name"], + ReadsExactly("Monty") + ), + ) + the_actor.should( See.the(BodyOfTheLastResponse(), ReadsExactly("To be, or not to be")) ) """ + body_parts: list[str | int] + + def __getitem__(self, key: str) -> BodyOfTheLastResponse: + self.body_parts.append(key) + return self + + def __init__(self) -> None: + self.body_parts = [] + def describe(self) -> str: """Describe the Question..""" return "The body of the last response." @@ -40,6 +61,9 @@ def answered_by(self, the_actor: Actor) -> Union[dict, str]: if len(responses) < 1: raise UnableToAnswer(f"{the_actor} has not yet received any API responses.") try: - return responses[-1].json() + response = responses[-1].json() + for part in self.body_parts: + response = response.__getitem__(part) + return response except JSONDecodeError: return responses[-1].text diff --git a/tests/conftest.py b/tests/conftest.py index 1982591..01c11db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,6 @@ from unittest import mock import pytest - from screenpy import AnActor from screenpy_requests.abilities import MakeAPIRequests diff --git a/tests/test_actions.py b/tests/test_actions.py index 856e705..1c9ae6d 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -1,4 +1,5 @@ import logging + import pytest from screenpy_requests.abilities import MakeAPIRequests @@ -13,8 +14,8 @@ SendPATCHRequest, SendPOSTRequest, SendPUTRequest, - generate_send_method_class, SetHeaders, + generate_send_method_class, ) diff --git a/tests/test_questions.py b/tests/test_questions.py index d39748d..4e8dc56 100644 --- a/tests/test_questions.py +++ b/tests/test_questions.py @@ -2,7 +2,6 @@ from unittest import mock import pytest - from requests.cookies import RequestsCookieJar from screenpy.exceptions import UnableToAnswer @@ -53,6 +52,22 @@ def test_ask_for_body_of_the_last_response(self, APITester): assert BodyOfTheLastResponse().answered_by(APITester) == test_json + def test_stores_index_path(self): + botlr = BodyOfTheLastResponse()["shuffled"]["off"][10]["mortal"]["coils"] + + assert botlr.body_parts == ["shuffled", "off", 10, "mortal", "coils"] + + def test_digs_into_json(self, APITester): + test_json = {"plays": [{"name": "Hamlet"}]} + fake_response = mock.Mock() + fake_response.json.return_value = test_json + mocked_mar = APITester.ability_to(MakeAPIRequests) + mocked_mar.responses = [fake_response] + + botlr = BodyOfTheLastResponse()["plays"][0]["name"] + + assert botlr.answered_by(APITester) == "Hamlet" + class TestCookies: def test_can_be_instantiated(self): From 0a2858c821385136bb29e9dd846d3b8b51788916 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Mon, 12 Feb 2024 16:26:42 -0600 Subject: [PATCH 2/8] #6: use subscript instead of __getitem__. --- screenpy_requests/questions/body_of_the_last_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 3027c22..e47d561 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -63,7 +63,7 @@ def answered_by(self, the_actor: Actor) -> Union[dict, str]: try: response = responses[-1].json() for part in self.body_parts: - response = response.__getitem__(part) + response = response[part] return response except JSONDecodeError: return responses[-1].text From 4bb1de6adaf29736536d0a23c237c89a0663ec19 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Mon, 12 Feb 2024 16:31:54 -0600 Subject: [PATCH 3/8] #6: add ability to slice text responses, too, why not. --- .../questions/body_of_the_last_response.py | 5 ++++- tests/test_questions.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index e47d561..9b888ef 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -66,4 +66,7 @@ def answered_by(self, the_actor: Actor) -> Union[dict, str]: response = response[part] return response except JSONDecodeError: - return responses[-1].text + response = responses[-1].text + for part in self.body_parts: + response = response[part] + return response diff --git a/tests/test_questions.py b/tests/test_questions.py index 4e8dc56..cd40579 100644 --- a/tests/test_questions.py +++ b/tests/test_questions.py @@ -68,6 +68,16 @@ def test_digs_into_json(self, APITester): assert botlr.answered_by(APITester) == "Hamlet" + def test_slices_text(self, APITester): + test_text = "My favorite play is Hamlet." + fake_response = mock.Mock() + fake_response.json.return_value = test_text + mocked_mar = APITester.ability_to(MakeAPIRequests) + mocked_mar.responses = [fake_response] + + assert BodyOfTheLastResponse()[-7:-1].answered_by(APITester) == "Hamlet" + + class TestCookies: def test_can_be_instantiated(self): From 73dbe483cbf5c82b461c28b71fbba1ed4fe07c88 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Mon, 12 Feb 2024 16:35:08 -0600 Subject: [PATCH 4/8] #6: add additional examples for slicing. --- .../questions/body_of_the_last_response.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 9b888ef..732e1db 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -20,15 +20,19 @@ class BodyOfTheLastResponse: If you are expecting a JSON response, this Question can be indexed to return only a section of that response. See the examples below. + If you are expecting a text response, you can also slice the text. + Abilities Required: :class:`~screenpy_requests.abilities.MakeAPIRequests` Examples:: + # JSON response the_actor.should( See.the(BodyOfTheLastResponse(), ContainsTheEntry(play="Hamlet")) ) + # JSON response, picked the_actor.should( See.the( BodyOfTheLastResponse()["users"][0]["first_name"], @@ -36,9 +40,15 @@ class BodyOfTheLastResponse: ), ) + # text response the_actor.should( See.the(BodyOfTheLastResponse(), ReadsExactly("To be, or not to be")) ) + + # text response, sliced + the_actor.should( + See.the(BodyOfTheLastResponse()[6:12], ReadsExactly("Python")) + ) """ body_parts: list[str | int] From b6db106e6ac434ad5fb29b37584809fbc3c261b9 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Tue, 13 Feb 2024 11:54:01 -0600 Subject: [PATCH 5/8] #6: fix type hint for getitem. --- screenpy_requests/questions/body_of_the_last_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 732e1db..6aed833 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -53,7 +53,7 @@ class BodyOfTheLastResponse: body_parts: list[str | int] - def __getitem__(self, key: str) -> BodyOfTheLastResponse: + def __getitem__(self, key: str | int) -> BodyOfTheLastResponse: self.body_parts.append(key) return self From 6de10382c51730e7046ae043eadb0d728d1816a1 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Tue, 13 Feb 2024 12:11:47 -0600 Subject: [PATCH 6/8] #6: fix type hint for getitem, again. --- screenpy_requests/questions/body_of_the_last_response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 6aed833..051c834 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -51,9 +51,9 @@ class BodyOfTheLastResponse: ) """ - body_parts: list[str | int] + body_parts: list[str | int | slice] - def __getitem__(self, key: str | int) -> BodyOfTheLastResponse: + def __getitem__(self, key: str | int | slice) -> BodyOfTheLastResponse: self.body_parts.append(key) return self From 3476d05b93f73491641db02a49a7252d23ca2f29 Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Tue, 13 Feb 2024 12:34:30 -0600 Subject: [PATCH 7/8] #6: change argument name to be clearer (h/t @bandophahita). --- screenpy_requests/questions/body_of_the_last_response.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 051c834..7b0bffe 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -53,8 +53,8 @@ class BodyOfTheLastResponse: body_parts: list[str | int | slice] - def __getitem__(self, key: str | int | slice) -> BodyOfTheLastResponse: - self.body_parts.append(key) + def __getitem__(self, subscript: str | int | slice) -> BodyOfTheLastResponse: + self.body_parts.append(subscript) return self def __init__(self) -> None: From 23df87b9966701306e17d0e7929ee8b39b2ee49b Mon Sep 17 00:00:00 2001 From: Perry Goy Date: Tue, 13 Feb 2024 12:47:41 -0600 Subject: [PATCH 8/8] use a typealias for subscripts type (h/t @bandophahita). --- .../questions/body_of_the_last_response.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/screenpy_requests/questions/body_of_the_last_response.py b/screenpy_requests/questions/body_of_the_last_response.py index 7b0bffe..8852e62 100644 --- a/screenpy_requests/questions/body_of_the_last_response.py +++ b/screenpy_requests/questions/body_of_the_last_response.py @@ -13,6 +13,8 @@ from ..abilities import MakeAPIRequests +subscripts = Union[str, int, slice] + class BodyOfTheLastResponse: """Ask about the body of the last API response received by the Actor. @@ -51,10 +53,10 @@ class BodyOfTheLastResponse: ) """ - body_parts: list[str | int | slice] + body_parts: list[subscripts] - def __getitem__(self, subscript: str | int | slice) -> BodyOfTheLastResponse: - self.body_parts.append(subscript) + def __getitem__(self, key: subscripts) -> BodyOfTheLastResponse: + self.body_parts.append(key) return self def __init__(self) -> None: @@ -65,7 +67,7 @@ def describe(self) -> str: return "The body of the last response." @beat("{} examines the body of the last response they received.") - def answered_by(self, the_actor: Actor) -> Union[dict, str]: + def answered_by(self, the_actor: Actor) -> dict | str: """Direct the Actor to investigate the body of the last response.""" responses = the_actor.ability_to(MakeAPIRequests).responses if len(responses) < 1: