Skip to content

Commit

Permalink
pokedex#2: "introduces pokemon fetch by move" (#11)
Browse files Browse the repository at this point in the history
### Summary:

Introduces the `get_pokemon_by_move` entrypoint. To navigate the paginated "move/" endpoint, the `search_endpoint` functionality is introduced. Refactors previous entrypoint to leverage new, more general client code.

### Notes:

* closes issue #2
  • Loading branch information
withtwoemms committed May 29, 2023
1 parent f9b7db6 commit 48e5917
Show file tree
Hide file tree
Showing 9 changed files with 1,213 additions and 33 deletions.
10 changes: 9 additions & 1 deletion pokedex/api/__main__.py
Expand Up @@ -2,7 +2,7 @@
import sys
from argparse import ArgumentParser

from pokedex.api.client import get_pokemon_by_type
from pokedex.api.client import get_pokemon_by_move, get_pokemon_by_type


def go(args=sys.argv):
Expand All @@ -12,6 +12,7 @@ def go(args=sys.argv):

parser_by = subparsers.add_parser("by")
parser_by.add_argument("--type", type=str, help="e.g. water, grass, fire")
parser_by.add_argument("--move", type=str, help='e.g. "water gun", "razor leaf", ember')

any_args_given = len(sys.argv) > 1

Expand All @@ -20,7 +21,14 @@ def go(args=sys.argv):
sys.exit(1)

args = parser.parse_args()

if args.type:
for pokemon in get_pokemon_by_type(args.type):
output = json.dumps(pokemon, indent=4)
print(output)

if args.move:
move = str(args.move).replace(" ", "-")
for pokemon in get_pokemon_by_move(move):
output = json.dumps(pokemon, indent=4)
print(output)
71 changes: 47 additions & 24 deletions pokedex/api/client.py
@@ -1,13 +1,11 @@
from typing import Any, Dict, Generator, Iterable, List, Optional, Union
from typing import Any, Dict, Generator, Iterable, List, Optional

import requests

from pokedex.api.constants import BASE_URL
from pokedex.api.models import PokeApiRequest, PokeApiResource, PokeApiResourceRef, PokemonRef

PokeApiEndpoints = Dict[str, str]
PokemonTypeRefs = List[PokeApiResourceRef]
PokemonTypes = Dict[str, Union[Optional[str], List[Dict[str, str]]]]
Pokemon = Dict[str, Any]


Expand All @@ -22,26 +20,22 @@ def select_endpoint(name: str, endpoints: PokeApiEndpoints) -> PokeApiRequest:
return PokeApiRequest(endpoints[name])


def get_pokemon_type_refs(request: PokeApiRequest) -> PokemonTypeRefs:
def get_resource(request: PokeApiRequest) -> PokeApiResource:
response: requests.Response = request()
response.raise_for_status() # TODO: handle error states
pokemon_type_resource = PokeApiResource(**response.json())
pokemon_type_refs: List[PokeApiResourceRef] = pokemon_type_resource.results
return pokemon_type_refs
return PokeApiResource(**response.json())


def select_pokemon_type(pokemon_type: str, pokemon_type_refs: PokemonTypeRefs) -> Optional[PokeApiRequest]:
for pokemon_type_ref in pokemon_type_refs:
if pokemon_type_ref.name == pokemon_type:
return pokemon_type_ref.as_request()


def get_pokemon_refs(pokemon_type_request: PokeApiRequest) -> Generator[PokemonRef, None, None]:
response: requests.Response = pokemon_type_request()
def generate_pokemon_requests(api_request: PokeApiRequest, response_key: str) -> Generator[PokeApiRequest, None, None]:
response: requests.Response = api_request()
response.raise_for_status() # TODO: handle error states
pokemon_refs = response.json()["pokemon"]
for pokemon_ref in pokemon_refs:
yield PokemonRef(**pokemon_ref).as_request()
resource_refs = response.json()[response_key]
for resource_ref in resource_refs:
if response_key == "pokemon":
model = PokemonRef(**resource_ref).to_api_resource_ref()
else:
model = PokeApiResourceRef(**resource_ref)
yield model.as_request()


# TODO: make concurrent
Expand All @@ -52,10 +46,39 @@ def get_pokemon(pokemon_requests: Iterable[PokeApiRequest]) -> Generator[Pokemon
yield response.json()


def get_pokemon_by_type(pokemon_type: str):
def search_endpoint(
endpoint_name: str, resource_ref_name: str, api_resource: Optional[PokeApiResource] = None
) -> Optional[PokeApiRequest]:
if not api_resource:
api_resource = fetch(endpoint_name)

resource_refs: List[PokeApiResourceRef] = api_resource.results
for ref in resource_refs:
if ref.name == resource_ref_name:
return ref.as_request()

if api_resource.next_request:
response: requests.Response = api_resource.next_request()
response.raise_for_status() # TODO: handle error states
new_api_resource = PokeApiResource(**response.json())
return search_endpoint(endpoint_name, resource_ref_name, new_api_resource)


def fetch(endpoint_name: str) -> PokeApiResource:
endpoints = get_endpoints(PokeApiRequest(BASE_URL))
endpoint = select_endpoint("type", endpoints)
pokemon_type_refs = get_pokemon_type_refs(endpoint)
pokemon_refs_request = select_pokemon_type(pokemon_type, pokemon_type_refs)
pokemon_refs = get_pokemon_refs(pokemon_refs_request)
yield from get_pokemon(pokemon_refs)
endpoint = select_endpoint(endpoint_name, endpoints)
return get_resource(endpoint)


def get_pokemon_by_move(pokemon_move: str) -> Generator[Pokemon, None, None]:
endpoint_request = search_endpoint("move", pokemon_move)
if endpoint_request:
pokemon_refs = generate_pokemon_requests(endpoint_request, "learned_by_pokemon")
yield from get_pokemon(pokemon_refs)


def get_pokemon_by_type(pokemon_type: str):
endpoint_request = search_endpoint("type", pokemon_type)
if endpoint_request:
pokemon_refs = generate_pokemon_requests(endpoint_request, "pokemon")
yield from get_pokemon(pokemon_refs)
8 changes: 8 additions & 0 deletions pokedex/api/models.py
Expand Up @@ -27,6 +27,9 @@ class PokemonRef(BaseModel):
pokemon: PokeApiResourceRef
slot: int

def to_api_resource_ref(self) -> PokeApiRequest:
return self.pokemon

def as_request(self) -> PokeApiRequest:
return self.pokemon.as_request()

Expand All @@ -36,3 +39,8 @@ class PokeApiResource(BaseModel):
next: Optional[str]
previous: Optional[str]
results: List[PokeApiResourceRef]

@property
def next_request(self) -> Optional[PokeApiRequest]:
if self.next:
return PokeApiRequest(self.next)
7 changes: 5 additions & 2 deletions tests/fixtures/__init__.py
Expand Up @@ -7,9 +7,12 @@

resource_paths: Dict[str, Path] = {
"endpoints.response": parentdir / "endpoints.response.json",
"pokemon.types.response": parentdir / "pokemon.types.response.json",
"pokemon.refs.fairy.response": parentdir / "pokemon.refs.fairy.response.json",
"jigglypuff.response": parentdir / "jigglypuff.response.json",
"pokemon.move.headbutt.response": parentdir / "pokemon.move.headbutt.response.json",
"pokemon.moves.response.1": parentdir / "pokemon.moves.response.1.json",
"pokemon.moves.response.2": parentdir / "pokemon.moves.response.2.json",
"pokemon.type.fairy.response": parentdir / "pokemon.type.fairy.response.json",
"pokemon.types.response": parentdir / "pokemon.types.response.json",
}


Expand Down

0 comments on commit 48e5917

Please sign in to comment.