Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Navi-27/Proyecto-UPC/llms.txt

Use this file to discover all available pages before exploring further.

The PokeAPI class in services/poke_api.py is the bridge between the application and the PokéAPI REST API. It implements a cache-first strategy using the local SQLite cache_pokemon table: network requests are only made when the cache is empty or when a specific Pokémon is not found in it. This dramatically reduces API call volume after the first run and keeps the application functional even during transient network outages.

Configuration

BASE_URL
str
The root URL for all PokéAPI requests: https://pokeapi.co/api/v2

Cache-First Strategy

On every application startup, the index route calls api.validacion() to decide how to populate the Pokédex:
validacion = api.validacion()
if not validacion:
    pokedex = api.obtener_lista_pokemones(limite=1025)
else:
    pokedex = api.cargar_desde_db()
  • If validacion() returns False (cache is empty), obtener_lista_pokemones(limite=1025) is called. This makes 1 026 HTTP requests — one for the list endpoint and one per Pokémon — then persists everything to cache_pokemon via Pokedex.guardar_en_db().
  • If validacion() returns True (cache is populated), cargar_desde_db() reads all rows from SQLite and constructs Pokemon objects with no network I/O whatsoever.
The initial population of the cache (1 025 Pokémon) prints progress to stdout as Descargando {i+1}/1025... for every Pokémon fetched. This output is normal and expected — it only occurs once, before the cache is written.

Method Reference

Checks whether the cache_pokemon table contains any rows.
returns
bool
True if COUNT(*) is greater than zero (cache is populated); False if the table is empty.
Query executed
SELECT COUNT(*) FROM cache_pokemon
Source
def validacion(self):
    conn = get_connection()
    count = conn.execute("SELECT COUNT(*) FROM cache_pokemon").fetchone()[0]
    if count == 0:
        return False
    else:
        return True
Loads the entire cache_pokemon table from SQLite and returns a fully populated Pokedex instance. This is the primary path for all requests after the first startup.
returns
Pokedex
A Pokedex instance containing one Pokemon object per row in cache_pokemon. The tipos and stats columns are JSON strings in the database; this method deserializes them with json.loads before constructing each Pokemon.
Source
def cargar_desde_db(self):
    conn = get_connection()
    rows = conn.execute("SELECT * FROM cache_pokemon").fetchall()
    conn.close()
    pokedex = Pokedex()
    for row in rows:
        pokemon = Pokemon(
            id=row["id"],
            nombre=row["nombre"],
            tipos=json.loads(row["tipos"]),
            altura=row["altura"],
            peso=row["peso"],
            imagen=row["imagen"],
            stats=json.loads(row["stats"])
        )
        pokedex.agregar_pokemon(pokemon)
    return pokedex
Fetches a batch of Pokémon from PokéAPI, constructs Pokemon objects, persists them to the SQLite cache, and returns a populated Pokedex. This method is called only when the cache is empty.
limite
int
required
The number of Pokémon to fetch. The application always calls this with limite=1025 to load the full National Pokédex.
offset
int
The starting index for pagination. Defaults to 0.
returns
Pokedex
A Pokedex instance containing all fetched Pokémon, already persisted to cache_pokemon.
Endpoints called
  1. GET https://pokeapi.co/api/v2/pokemon?limit={limite}&offset={offset} — retrieves a paginated list of Pokémon names and individual URLs.
  2. For each result: GET {item["url"]} — retrieves full data for that Pokémon.
Sprite URL constructionRather than using the sprite URL returned by the API, the method constructs a deterministic GitHub raw URL:
https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{numero}.png
where numero = offset + i + 1 (the 1-based Pokédex position).Source
def obtener_lista_pokemones(self, limite, offset=0):
    url = f"{self.BASE_URL}/pokemon?limit={limite}&offset={offset}"
    respuesta = requests.get(url)
    datos = respuesta.json()

    pokedex = Pokedex()

    for i, item in enumerate(datos["results"]):
        print(f"Descargando {i+1}/{limite}...")
        url = item["url"]
        respuesta = requests.get(url)
        datos_pokemon = respuesta.json()

        numero = offset + i + 1
        imagen = f"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{numero}.png"
        pokemon = Pokemon(
            id=numero,
            nombre=datos_pokemon["name"],
            tipos=[t["type"]["name"] for t in datos_pokemon["types"]] if "types" in datos_pokemon else [],
            altura=datos_pokemon["height"] if "height" in datos_pokemon else 0,
            peso=datos_pokemon["weight"] if "weight" in datos_pokemon else 0,
            imagen=imagen,
            stats={s["stat"]["name"]: s["base_stat"] for s in datos_pokemon["stats"]} if "stats" in datos_pokemon else {}
        )
        pokedex.agregar_pokemon(pokemon)
    pokedex.guardar_en_db()
    return pokedex
Retrieves a single Pokémon by name or numeric ID. This is the most sophisticated method in the class — it implements a full cache-first lookup with a live API fallback and automatic cache population on miss.
nombre_o_id
str | int
required
The Pokémon’s lowercase name (e.g. "pikachu") or its National Pokédex number (e.g. 25).
returns
Pokemon | None
A Pokemon instance on success. Returns None if the Pokémon is not found in the cache, the API returns a non-200 status, or any exception is raised.
Lookup flow
1. Query cache_pokemon WHERE nombre = ? OR id = ?
   └─ Hit  → construct and return Pokemon immediately
   └─ Miss ↓
2. GET https://pokeapi.co/api/v2/pokemon/{nombre_o_id}  (timeout=5s)
   └─ Non-200 → return None
   └─ 200 ↓
3. Construct Pokemon from API response
4. INSERT OR IGNORE into cache_pokemon
5. Return Pokemon
Source
def obtener_pokemon(self, nombre_o_id):
    # 1. Buscar en caché primero
    try:
        conn = get_connection()
        row = conn.execute(
            "SELECT * FROM cache_pokemon WHERE nombre = ? OR id = ?",
            (str(nombre_o_id), str(nombre_o_id))
        ).fetchone()
        conn.close()

        if row:
            return Pokemon(
                id=row["id"],
                nombre=row["nombre"],
                tipos=json.loads(row["tipos"]),
                altura=row["altura"],
                peso=row["peso"],
                imagen=row["imagen"],
                stats=json.loads(row["stats"])
            )
    except:
        pass

    # 2. Si no está en caché, llamar la API
    try:
        url = f"{self.BASE_URL}/pokemon/{nombre_o_id}"
        respuesta = requests.get(url, timeout=5)

        if respuesta.status_code != 200:
            return None

        datos = respuesta.json()

        pokemon = Pokemon(
            id=datos["id"],
            nombre=datos["name"],
            tipos=[t["type"]["name"] for t in datos["types"]],
            altura=datos["height"],
            peso=datos["weight"],
            imagen=datos["sprites"]["front_default"],
            stats={s["stat"]["name"]: s["base_stat"] for s in datos["stats"]}
        )

        # 3. Guardar en caché
        try:
            conn = get_connection()
            conn.execute(
                "INSERT OR IGNORE INTO cache_pokemon (id, nombre, tipos, altura, peso, imagen, stats) VALUES (?,?,?,?,?,?,?)",
                (
                    pokemon.id,
                    pokemon.nombre,
                    json.dumps(pokemon.tipos),
                    pokemon.altura,
                    pokemon.peso,
                    pokemon.imagen,
                    json.dumps(pokemon.stats)
                )
            )
            conn.commit()
            conn.close()
        except:
            pass
        return pokemon
    except:
        return None
When the full cache is loaded via obtener_lista_pokemones, almost every call to obtener_pokemon will be served from the cache (step 1). The API fallback in step 2 is primarily useful for Pokémon added after the initial bulk fetch, or when looking up a Pokémon by a name that wasn’t in the original batch.
Queries the SQLite cache for all Pokémon whose tipos column contains the given type string, and returns them as a list of Pokemon objects.
tipo
str
required
The type name to filter by (e.g. "fire", "water", "dragon"). Case-sensitive — use lowercase to match stored values.
returns
list[Pokemon]
A list of Pokemon objects whose stored tipos JSON string contains the given type. Returns an empty list if no matches are found.
Query executed
SELECT * FROM cache_pokemon WHERE tipos LIKE '%{tipo}%'
Source
def obtener_por_tipo(self, tipo):
    conn = get_connection()
    rows = conn.execute(
        "SELECT * FROM cache_pokemon WHERE tipos LIKE ?", (f'%{tipo}%',)
    ).fetchall()
    conn.close()
    pokemones = []
    for row in rows:
        pokemon = Pokemon(
            id=row["id"],
            nombre=row["nombre"],
            tipos=json.loads(row["tipos"]),
            altura=row["altura"],
            peso=row["peso"],
            imagen=row["imagen"],
            stats=json.loads(row["stats"]))
        pokemones.append(pokemon)
    return pokemones
obtener_por_tipo uses a LIKE '%tipo%' query against the raw JSON string stored in the tipos column. Because tipos stores an array of type name strings (e.g. ["ice", "flying"]), a search for "ice" could theoretically match any substring of that JSON — including a Pokémon name if it appeared inside the tipos field. In practice, since tipos contains only PokéAPI type names, false positives are extremely unlikely. For a more robust solution, consider filtering results in Python after deserializing the tipos JSON.

Data Flow Diagram

Application startup


 api.validacion()

  ┌────┴────┐
  │ False   │ True
  ▼         ▼
obtener_  cargar_
lista_    desde_
pokemones db()
(HTTP)    (SQLite)
  │         │
  └────┬────┘

   Pokedex object
   in memory

  ┌────┴────────────────────┐
  │                         │
  ▼                         ▼
index route            detalle route
(listing)         obtener_pokemon(nombre)

                  ┌────┴────┐
                  │ Cache   │ Miss
                  │ hit     ▼
                  │     GET /pokemon/{id}
                  │     (timeout=5s)
                  │         │
                  └────┬────┘

                  Pokemon object

Build docs developers (and LLMs) love