diff --git a/.circleci/config.yml b/.circleci/config.yml
index cc091f507..81b5b69f6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -16,7 +16,7 @@ jobs:
command: make install
- run:
name: Pre-commit checks
- command: SKIP=ruff-format uv run pre-commit run --all-files --show-diff-on-failure
+ command: SKIP=ruff-format,ruff-check,ty-check,generate-openapi uv run pre-commit run --all-files --show-diff-on-failure
- run:
name: Format Check
command: make format-check
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
new file mode 100644
index 000000000..c7c2e78c4
--- /dev/null
+++ b/.github/workflows/code-quality.yml
@@ -0,0 +1,79 @@
+name: Code Quality
+
+on:
+ pull_request:
+ paths:
+ - "**.py"
+ - "**.toml"
+ - "Makefile"
+ - "uv.lock"
+ - ".github/workflows/code-quality.yml"
+ push:
+ branches-ignore:
+ - master
+ - staging
+ paths:
+ - "**.py"
+ - "**.toml"
+ - "Makefile"
+ - "uv.lock"
+ - ".github/workflows/code-quality.yml"
+
+jobs:
+ ruff:
+ name: Ruff (lint)
+ runs-on: ubuntu-latest
+ # TODO: remove once the codebase is fully lint-clean
+ continue-on-error: true
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ - name: Setup uv
+ uses: ./.github/actions/setup-uv
+ - name: Install dependencies
+ run: make install
+ - name: Lint
+ run: make lint-check || true
+ - name: Summary
+ if: always()
+ run: |
+ {
+ echo "## Ruff lint results"
+ echo
+ echo "Expand to see all errors (backlog while the codebase is being linted incrementally)
"
+ echo
+ echo '```'
+ uv run ruff check --output-format=concise . || true
+ echo '```'
+ echo
+ echo " "
+ } >> "$GITHUB_STEP_SUMMARY"
+
+ ty:
+ name: ty (type-check)
+ runs-on: ubuntu-latest
+ # TODO: remove once the codebase is fully typed
+ continue-on-error: true
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ - name: Setup uv
+ uses: ./.github/actions/setup-uv
+ - name: Install dependencies
+ run: make install
+ - name: Type-check
+ run: make typecheck || true
+ - name: Summary
+ if: always()
+ run: |
+ {
+ echo "## ty type-check results"
+ echo
+ echo "Expand to see all errors (backlog while the codebase is being typed incrementally)
"
+ echo
+ echo '```'
+ uv run ty check --output-format=concise || true
+ echo '```'
+ echo
+ echo " "
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml
index c943ef06a..090a341b4 100644
--- a/.github/workflows/database.yml
+++ b/.github/workflows/database.yml
@@ -2,6 +2,10 @@ name: Database
on:
pull_request:
+ push:
+ branches-ignore:
+ - master
+ - staging
jobs:
csv:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e3bb6fcdf..36955dd17 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,14 +20,6 @@ repos:
args:
- --markdown-linebreak-ext=md
- # TODO: Enable in the future when adding the ruff linter
- # - repo: https://github.com/pre-commit/pygrep-hooks
- # rev: v1.9.0
- # hooks:
- # - id: python-check-blanket-noqa
- # - id: python-check-blanket-type-ignore
- # - id: python-use-type-annotations
-
- repo: local
hooks:
- id: check-csv
@@ -37,6 +29,13 @@ repos:
args: [--encoding, utf-8]
files: \.csv$
+ - id: ruff-check
+ name: Ruff Check
+ entry: uv run ruff check .
+ language: system
+ types_or: [python, pyi]
+ pass_filenames: false
+
- id: ruff-format
name: Ruff Format
entry: uv run ruff format .
@@ -44,6 +43,20 @@ repos:
types_or: [python, pyi]
pass_filenames: false
+ - id: ty-check
+ name: Ty Check
+ entry: uv run ty check
+ language: system
+ types_or: [python, pyi]
+ pass_filenames: false
+
+ - id: generate-openapi
+ name: Generate OpenAPI Schema
+ entry: uv run manage.py spectacular --file openapi.yml --settings=config.docker_compose
+ language: system
+ files: ^(pokemon_v2/|config/|openapi\.yml)
+ pass_filenames: false
+
- id: build-and-test
name: Build and Test
entry: >-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a03e87e19..3fa06a642 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,13 +18,17 @@ git checkout -b my_new_branch
```
- Write some code, fix something, and add a test to prove that it works. **No pull request will be accepted without tests passing, or without new tests if new features are added.**
-- Make sure your code passes the pre-commit hooks, if you have the hooks installed it should run automatically on commit. You can run them manually with:
+- Make sure your code changes passes the pre-commit hooks, linting, formatting, and typechecking. You can run them manually with:
```bash
-make pre-commit
-# or
-uv run pre-commit run --all-files
+make pre-commit # or: uv run pre-commit run --all-files
+make lint-check # or: uv run ruff check .
+make format # or: uv run ruff format .
+make typecheck # or: uv run ty check
```
+> [!NOTE]
+> As of right now we are not strictly enforcing linting and typechecking, but we will be in the future. Please try to make sure your code passes these checks.
+
- Commit your code and push it to GitHub
- [Open a new pull request](https://help.github.com/articles/creating-a-pull-request/) and describe the changes you have made.
diff --git a/Makefile b/Makefile
index f4d042445..68778b3f3 100755
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
veekun_pokedex_repository = ../pokedex
local_config = --settings=config.local
-docker_config = --settings=config.docker-compose
+docker_config = --settings=config.docker_compose
gql_compose_config_deprecated = -f docker-compose.yml -f docker-compose-dev.yml -f Resources/compose/docker-compose-prod-graphql.yml
gql_compose_config = -f docker-compose.yml -f Resources/compose/docker-compose-prod-graphql.yml
@@ -101,6 +101,15 @@ format: check-uv # Format the source code
format-check: check-uv # Check the source code has been formatted
uv run ruff format . --check
+lint-check: check-uv # Lint the source code
+ uv run ruff check .
+
+lint-fix: check-uv # Lint the source code and fix issues
+ uv run ruff check . --fix
+
+typecheck: check-uv # Type-check the source code with ty
+ uv run ty check
+
pull:
git checkout master
git pull
diff --git a/README.md b/README.md
index 327638441..6ae1d695f 100755
--- a/README.md
+++ b/README.md
@@ -54,6 +54,17 @@ A RESTful API for Pokémon - [pokeapi.co](https://pokeapi.co)
> [!NOTE]
> Pre-commit hooks are optional but recommended for maintaining code quality and consistency. If you do not want it to automatically run on every commit, you can run it manually with `make pre-commit` before commiting and pushing your changes.
+- Lint, format, and typecheck your code changes:
+
+ ```sh
+ make lint-check # or: uv run ruff check .
+ make format # or: uv run ruff format .
+ make typecheck # or: uv run ty check
+ ```
+
+> [!NOTE]
+> As of right now we are not strictly enforcing linting and typechecking, but we will be in the future. Please try to make sure your code passes these checks.
+
- Set up the local development environment using the following command:
```sh
@@ -107,8 +118,8 @@ If you don't have `make` on your machine you can use the following commands
```sh
docker compose up -d
-docker compose exec -T app python manage.py migrate --settings=config.docker-compose
-docker compose exec -T app sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker-compose'
+docker compose exec -T app python manage.py migrate --settings=config.docker_compose
+docker compose exec -T app sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker_compose'
```
Browse [localhost/api/v2/](http://localhost/api/v2/) or [localhost/api/v2/pokemon/bulbasaur/](http://localhost/api/v2/pokemon/bulbasaur/) on port `80`.
@@ -159,8 +170,8 @@ Configure `kubectl` to point to a cluster and then run the following commands to
kubectl apply -k Resources/k8s/kustomize/base/
kubectl config set-context --current --namespace pokeapi # (Optional) Set pokeapi ns as the working ns
# Wait for the cluster to spin up
-kubectl exec --namespace pokeapi deployment/pokeapi -- python manage.py migrate --settings=config.docker-compose # Migrate the DB
-kubectl exec --namespace pokeapi deployment/pokeapi -- sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker-compose' # Build the db
+kubectl exec --namespace pokeapi deployment/pokeapi -- python manage.py migrate --settings=config.docker_compose # Migrate the DB
+kubectl exec --namespace pokeapi deployment/pokeapi -- sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker_compose' # Build the db
kubectl wait --namespace pokeapi --timeout=120s --for=condition=complete job/load-graphql # Wait for Graphql configuration job to finish
```
diff --git a/Resources/docker/app/Dockerfile b/Resources/docker/app/Dockerfile
index 1405625b4..dec9772b4 100644
--- a/Resources/docker/app/Dockerfile
+++ b/Resources/docker/app/Dockerfile
@@ -18,7 +18,7 @@ FROM python:3.14-alpine
RUN apk add --no-cache git
ENV PYTHONUNBUFFERED=1
-ENV DJANGO_SETTINGS_MODULE='config.docker-compose'
+ENV DJANGO_SETTINGS_MODULE='config.docker_compose'
ENV PATH="/code/.venv/bin:$PATH"
WORKDIR /code
diff --git a/Resources/docker/app/README.md b/Resources/docker/app/README.md
index 28f2f50b5..626339a43 100644
--- a/Resources/docker/app/README.md
+++ b/Resources/docker/app/README.md
@@ -46,6 +46,6 @@ Pokémon data isn't automatically present in this image. All Pokémon data is pe
When the container is up and running, run the following shell commands:
```sh
-docker exec pokeapi python manage.py migrate --settings=config.docker-compose
-docker exec pokeapi sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker-compose'
+docker exec pokeapi python manage.py migrate --settings=config.docker_compose
+docker exec pokeapi sh -c 'echo "from data.v2.build import build_all; build_all()" | python manage.py shell --settings=config.docker_compose'
```
diff --git a/config/__init__.py b/config/__init__.py
old mode 100755
new mode 100644
diff --git a/config/asgi.py b/config/asgi.py
new file mode 100644
index 000000000..28a5fd2db
--- /dev/null
+++ b/config/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for PokeAPI.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
+
+application = get_asgi_application()
diff --git a/config/docker.py b/config/docker.py
old mode 100755
new mode 100644
index e8be3ed0f..7000aa198
--- a/config/docker.py
+++ b/config/docker.py
@@ -1,7 +1,9 @@
# Docker settings
-from .settings import *
+# ruff: noqa: F405
+# pyright: reportConstantRedefinition=false
+from .settings import * # noqa: F403
-DATABASES = {
+DATABASES: dict[str, DatabaseSettings] = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "pokeapi",
@@ -13,7 +15,7 @@
}
-CACHES = {
+CACHES: dict[str, CacheSettings] = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
@@ -24,3 +26,7 @@
}
DEBUG = True
+
+for template in TEMPLATES:
+ if "OPTIONS" in template and "debug" in template["OPTIONS"]:
+ template["OPTIONS"]["debug"] = DEBUG
diff --git a/config/docker-compose.py b/config/docker_compose.py
similarity index 74%
rename from config/docker-compose.py
rename to config/docker_compose.py
index 158a698ed..5a3e119e9 100644
--- a/config/docker-compose.py
+++ b/config/docker_compose.py
@@ -1,19 +1,22 @@
# Docker settings
+# ruff: noqa: F405
+# pyright: reportConstantRedefinition=false
import os
-from .settings import *
-DATABASES = {
+from .settings import * # noqa: F403
+
+DATABASES: dict[str, DatabaseSettings] = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("POSTGRES_DB", "pokeapi"),
"USER": os.environ.get("POSTGRES_USER", "ash"),
"PASSWORD": os.environ.get("POSTGRES_PASSWORD", "pokemon"),
"HOST": os.environ.get("POSTGRES_HOST", "db"),
- "PORT": os.environ.get("POSTGRES_PORT", 5432),
+ "PORT": os.environ.get("POSTGRES_PORT", "5432"),
}
}
-CACHES = {
+CACHES: dict[str, CacheSettings] = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.environ.get("REDIS_CONNECTION_STRING", "redis://cache:6379/1"),
diff --git a/config/local.py b/config/local.py
old mode 100755
new mode 100644
index d181da646..b6b81dd63
--- a/config/local.py
+++ b/config/local.py
@@ -1,16 +1,22 @@
-from .settings import *
+# pyright: reportConstantRedefinition=false
+# ruff: noqa: F405
+from .settings import * # noqa: F403
-DATABASES = {
+DATABASES: dict[str, DatabaseSettings] = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
-CACHES = {
+CACHES: dict[str, CacheSettings] = {
"default": {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
}
}
DEBUG = True
+
+for template in TEMPLATES:
+ if "OPTIONS" in template and "debug" in template["OPTIONS"]:
+ template["OPTIONS"]["debug"] = DEBUG
diff --git a/config/settings.py b/config/settings.py
old mode 100755
new mode 100644
index c85964262..3a033f40a
--- a/config/settings.py
+++ b/config/settings.py
@@ -1,14 +1,61 @@
+# ruff: noqa: E501
# Production settings
import os
+import textwrap
from pathlib import Path
+from typing import TypedDict
+
+
+class DatabaseSettings(TypedDict, total=False):
+ ENGINE: str
+ NAME: str | Path
+ USER: str
+ PASSWORD: str
+ HOST: str
+ PORT: str | int | None
+ CONN_MAX_AGE: int | None
+
+
+class CacheSettings(TypedDict, total=False):
+ BACKEND: str
+ LOCATION: str
+ OPTIONS: dict[str, str | int | None] | None
+
+
+class DRFSettings(TypedDict, total=False):
+ DEFAULT_RENDERER_CLASSES: tuple[str, ...]
+ DEFAULT_PARSER_CLASSES: tuple[str, ...]
+ DEFAULT_PAGINATION_CLASS: str | None
+ PAGE_SIZE: int | None
+ DEFAULT_SCHEMA_CLASS: str
-BASE_DIR = Path(__file__).resolve().parent.parent
-DEBUG = False
+class TemplateSettings(TypedDict, total=False):
+ BACKEND: str
+ DIRS: list[str]
+ APP_DIRS: bool
+ OPTIONS: dict[str, list[str] | bool | None]
-TEMPLATE_DEBUG = DEBUG
-ADMINS = (os.environ.get("ADMINS", "admin,admin@noemail.com").split(","),)
+class SpectacularSettings(TypedDict, total=False):
+ TITLE: str
+ DESCRIPTION: str
+ SORT_OPERATIONS: bool
+ SERVERS: list[dict[str, str]]
+ EXTERNAL_DOCS: dict[str, str]
+ VERSION: str
+ SERVE_INCLUDE_SCHEMA: bool
+ AUTHENTICATION_WHITELIST: list[str]
+ OAS_VERSION: str
+ COMPONENT_SPLIT_REQUEST: bool
+ TAGS: list[dict[str, str | dict[str, str]]]
+
+
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+DEBUG: bool = False
+
+ADMINS = [tuple(admin.split(",")) for admin in os.environ.get("ADMINS", "admin,admin@noemail.com").split(";")]
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
@@ -55,7 +102,7 @@
WSGI_APPLICATION = "config.wsgi.application"
-DATABASES = {
+DATABASES: dict[str, DatabaseSettings] = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "pokeapi_co_db",
@@ -67,7 +114,7 @@
}
}
-CACHES = {
+CACHES: dict[str, CacheSettings] = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
@@ -79,8 +126,6 @@
SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-a(!_5+l3$#l1f4n!x+&ns_+8$4q@df*3rh$n#2h@l$2gti7!7-")
-CUSTOM_APPS = ("pokemon_v2",)
-
INSTALLED_APPS = (
"django.contrib.admin",
"django.contrib.auth",
@@ -91,25 +136,25 @@
"rest_framework",
"cachalot",
"drf_spectacular",
-) + CUSTOM_APPS
+ "pokemon_v2",
+)
-CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_ALL_ORIGINS = True
-CORS_ALLOW_METHODS = "GET"
+CORS_ALLOW_METHODS = ("GET",)
CORS_URLS_REGEX = r"^/api/.*$"
-REST_FRAMEWORK = {
+REST_FRAMEWORK: DRFSettings = {
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
- "DEFAULT_PARSER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
+ "DEFAULT_PARSER_CLASSES": ("rest_framework.parsers.JSONParser",),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 20,
- "PAGINATE_BY": 20,
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
-TEMPLATES = [
+TEMPLATES: list[TemplateSettings] = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
@@ -120,30 +165,34 @@
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
+ "debug": DEBUG,
},
},
]
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
-SPECTACULAR_SETTINGS = {
+SPECTACULAR_SETTINGS: SpectacularSettings = {
"TITLE": "PokéAPI",
- "DESCRIPTION": """All the Pokémon data you'll ever need in one place, easily accessible through a modern free open-source RESTful API.
+ "DESCRIPTION": textwrap.dedent(
+ """
+ All the Pokémon data you'll ever need in one place, easily accessible through a modern free open-source RESTful API.
-## What is this?
+ ## What is this?
-This is a full RESTful API linked to an extensive database detailing everything about the Pokémon main game series.
+ This is a full RESTful API linked to an extensive database detailing everything about the Pokémon main game series.
-We've covered everything from Pokémon to Berry Flavors.
+ We've covered everything from Pokémon to Berry Flavors.
-## Where do I start?
+ ## Where do I start?
-We have awesome [documentation](https://pokeapi.co/docs/v2) on how to use this API. It takes minutes to get started.
+ We have awesome [documentation](https://pokeapi.co/docs/v2) on how to use this API. It takes minutes to get started.
-This API will always be publicly available and will never require any extensive setup process to consume.
+ This API will always be publicly available and will never require any extensive setup process to consume.
-Created by [**Paul Hallett**](https://github.com/phalt) and other [**PokéAPI contributors**](https://github.com/PokeAPI/pokeapi#contributing) around the world. Pokémon and Pokémon character names are trademarks of Nintendo.
- """,
+ Created by [**Paul Hallett**](https://github.com/phalt) and other [**PokéAPI contributors**](https://github.com/PokeAPI/pokeapi#contributing) around the world. Pokémon and Pokémon character names are trademarks of Nintendo.
+ """
+ ).strip(),
"SORT_OPERATIONS": False,
"SERVERS": [{"url": "https://pokeapi.co"}],
"EXTERNAL_DOCS": {"url": "https://pokeapi.co/docs/v2"},
diff --git a/config/urls.py b/config/urls.py
old mode 100755
new mode 100644
index 51fc6fc9f..70d969a27
--- a/config/urls.py
+++ b/config/urls.py
@@ -1,7 +1,6 @@
from django.urls import include, path
-from pokemon_v2 import urls as pokemon_v2_urls
-# pylint: disable=invalid-name
+from pokemon_v2 import urls as pokemon_v2_urls
urlpatterns = [
path("", include(pokemon_v2_urls)),
diff --git a/config/wsgi.py b/config/wsgi.py
old mode 100755
new mode 100644
index 5526cb895..7d219822b
--- a/config/wsgi.py
+++ b/config/wsgi.py
@@ -1,5 +1,5 @@
"""
-WSGI config for mysite project.
+WSGI config for PokeAPI.
It exposes the WSGI callable as a module-level variable named ``application``.
diff --git a/data/v2/build.py b/data/v2/build.py
index 79b464eb3..4a75fe6e5 100644
--- a/data/v2/build.py
+++ b/data/v2/build.py
@@ -15,8 +15,9 @@
import os
import os.path
import re
-import json
+
from django.db import connection
+
from pokemon_v2.models import *
# why this way? how about use `__file__`
@@ -43,16 +44,16 @@
)
IMAGE_DIR = os.getcwd() + "/data/v2/sprites/sprites/"
CRIES_DIR = os.getcwd() + "/data/v2/cries/cries/"
-RESOURCE_IMAGES = []
-RESOURCE_CRIES = []
+RESOURCE_IMAGES: list[str] = []
+RESOURCE_CRIES: list[str] = []
-for root, dirs, files in os.walk(IMAGE_DIR):
+for root, _dirs, files in os.walk(IMAGE_DIR):
for file in files:
image_path = os.path.join(root.replace(IMAGE_DIR, ""), file)
image_path = image_path.replace("\\", "/") # convert Windows-style path to Unix
RESOURCE_IMAGES.append(image_path)
-for root, dirs, files in os.walk(CRIES_DIR):
+for root, _dirs, files in os.walk(CRIES_DIR):
for file in files:
cry_path = os.path.join(root.replace(CRIES_DIR, ""), file)
cry_path = cry_path.replace("\\", "/") # convert Windows-style path to Unix
@@ -69,13 +70,12 @@ def with_iter(context, iterable=None):
if iterable is None:
iterable = context
with context:
- for value in iterable:
- yield value
+ yield from iterable
def load_data(file_name):
# with_iter closes the file when it has finished
- return csv.reader(with_iter(open(DATA_LOCATION + file_name, "rt", encoding="utf8")), delimiter=",")
+ return csv.reader(with_iter(open(DATA_LOCATION + file_name, encoding="utf8")), delimiter=",")
def clear_table(model):
@@ -445,7 +445,7 @@ def csv_record_to_objects(info):
elif re.search(r"^hm[0-9]", info[1]):
file_name = "hm-normal.png"
else:
- file_name = "%s.png" % info[1]
+ file_name = f"{info[1]}.png"
item_sprites = "items/{0}"
sprites = {"default": file_path_or_none(item_sprites.format(file_name))}
@@ -594,8 +594,8 @@ def csv_record_to_objects(info):
"generation-ix": ["scarlet-violet"],
}
sprites = {}
- for generation in game_map.keys():
- for game in game_map[generation]:
+ for generation, games in game_map.items():
+ for game in games:
if generation not in sprites:
sprites[generation] = {}
sprites[generation][game] = {
@@ -786,12 +786,12 @@ def csv_record_to_objects(info):
build_generic((MoveFlavorText,), "move_flavor_text.csv", csv_record_to_objects)
+ existing_effect_ids = set(MoveEffect.objects.values_list("pk", flat=True))
+
def csv_record_to_objects(info):
- _move_effect = None
- try:
- _move_effect = MoveEffect.objects.get(pk=int(info[6])) if info[6] != "" else None
- except:
- pass
+ effect_id = int(info[6]) if info[6] != "" else None
+ if effect_id not in existing_effect_ids:
+ effect_id = None
yield MoveChange(
move_id=int(info[0]),
@@ -800,7 +800,7 @@ def csv_record_to_objects(info):
power=int(info[3]) if info[3] != "" else None,
pp=int(info[4]) if info[4] != "" else None,
accuracy=int(info[5]) if info[5] != "" else None,
- move_effect_id=_move_effect.pk if _move_effect else None,
+ move_effect_id=effect_id,
move_effect_chance=int(info[7]) if info[7] != "" else None,
)
@@ -1148,7 +1148,7 @@ def csv_record_to_objects(info):
id=int(info[0]),
location_id=int(info[1]),
game_index=int(info[2]),
- name=("{}-{}".format(location.name, info[3]) if info[3] else "{}-{}".format(location.name, "area")),
+ name=(f"{location.name}-{info[3]}" if info[3] else "{}-{}".format(location.name, "area")),
)
build_generic((LocationArea,), "location_areas.csv", csv_record_to_objects)
@@ -1283,14 +1283,14 @@ def try_image_names(path, info, extension):
identifier = info[1]
species_id = info[2]
if "-" in identifier:
- form_file_name = "%s.%s" % (
+ form_file_name = "{}.{}".format(
species_id + "-" + identifier.split("-", 1)[1],
extension,
)
- id_file_name = "%s.%s" % (pokemon_id, extension)
+ id_file_name = f"{pokemon_id}.{extension}"
file_name = id_file_name if file_path_or_none(path + id_file_name) else form_file_name
else:
- file_name = "%s.%s" % (info[0], extension)
+ file_name = f"{info[0]}.{extension}"
return file_path_or_none(path + file_name)
def csv_record_to_objects(info):
@@ -1727,7 +1727,7 @@ def csv_record_to_objects(info):
build_generic((PokemonSprites,), "pokemon.csv", csv_record_to_objects)
def try_cry_names(path, info, extension):
- file_name = "%s.%s" % (info[0], extension)
+ file_name = f"{info[0]}.{extension}"
return file_path_or_none(path + file_name, image_file=False)
def csv_record_to_objects(info):
@@ -1834,42 +1834,42 @@ def csv_record_to_objects(info):
build_generic((PokemonForm,), "pokemon_forms.csv", csv_record_to_objects)
- def try_image_names(path, info, extension):
+ def try_form_image_names(path, info, extension):
form_identifier = info[2]
pokemon_id = info[3]
pokemon = Pokemon.objects.get(pk=int(pokemon_id))
- species_id = getattr(pokemon, "pokemon_species_id")
+ species_id = getattr(pokemon.pokemon_species, "pk", 0)
is_default = int(info[5])
if form_identifier:
- form_file_name = "%s-%s.%s" % (species_id, form_identifier, extension)
- id_file_name = "%s.%s" % (pokemon_id, extension)
+ form_file_name = f"{species_id}-{form_identifier}.{extension}"
+ id_file_name = f"{pokemon_id}.{extension}"
file_name = id_file_name if file_path_or_none(path + id_file_name) else form_file_name
if id_file_name and form_file_name and (not is_default):
file_name = form_file_name
else:
- file_name = "%s.%s" % (species_id, extension)
+ file_name = f"{species_id}.{extension}"
return file_path_or_none(path + file_name)
def csv_record_to_objects(info):
poke_sprites = "pokemon/"
sprites = {
- "front_default": try_image_names(poke_sprites, info, "png"),
- "front_shiny": try_image_names(poke_sprites + "shiny/", info, "png"),
- "back_default": try_image_names(poke_sprites + "back/", info, "png"),
- "back_shiny": try_image_names(poke_sprites + "back/shiny/", info, "png"),
- "front_female": try_image_names(poke_sprites + "female/", info, "png"),
- "front_shiny_female": try_image_names(poke_sprites + "shiny/female/", info, "png"),
- "back_female": try_image_names(poke_sprites + "back/female/", info, "png"),
- "back_shiny_female": try_image_names(poke_sprites + "back/shiny/female/", info, "png"),
+ "front_default": try_form_image_names(poke_sprites, info, "png"),
+ "front_shiny": try_form_image_names(poke_sprites + "shiny/", info, "png"),
+ "back_default": try_form_image_names(poke_sprites + "back/", info, "png"),
+ "back_shiny": try_form_image_names(poke_sprites + "back/shiny/", info, "png"),
+ "front_female": try_form_image_names(poke_sprites + "female/", info, "png"),
+ "front_shiny_female": try_form_image_names(poke_sprites + "shiny/female/", info, "png"),
+ "back_female": try_form_image_names(poke_sprites + "back/female/", info, "png"),
+ "back_shiny_female": try_form_image_names(poke_sprites + "back/shiny/female/", info, "png"),
"versions": {
"generation-viii": {
"brilliant-diamond-shining-pearl": {
- "front_default": try_image_names(
+ "front_default": try_form_image_names(
poke_sprites + "versions/generation-viii/brilliant-diamond-shining-pearl/",
info,
"png",
),
- "front_female": try_image_names(
+ "front_female": try_form_image_names(
poke_sprites + "versions/generation-viii/brilliant-diamond-shining-pearl/female/",
info,
"png",
@@ -1878,12 +1878,12 @@ def csv_record_to_objects(info):
},
"generation-ix": {
"scarlet-violet": {
- "front_default": try_image_names(
+ "front_default": try_form_image_names(
poke_sprites + "versions/generation-ix/scarlet-violet/",
info,
"png",
),
- "front_female": try_image_names(
+ "front_female": try_form_image_names(
poke_sprites + "versions/generation-ix/scarlet-violet/female/",
info,
"png",
diff --git a/manage.py b/manage.py
index aabb81818..f1aba7954 100755
--- a/manage.py
+++ b/manage.py
@@ -5,7 +5,7 @@
import sys
-def main():
+def main() -> None:
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
try:
diff --git a/openapi.yml b/openapi.yml
index 396d56759..9241f485b 100644
--- a/openapi.yml
+++ b/openapi.yml
@@ -2,16 +2,22 @@ openapi: 3.1.0
info:
title: PokéAPI
version: 2.10.0
- description: "All the Pokémon data you'll ever need in one place, easily accessible
- through a modern free open-source RESTful API.\n\n## What is this?\n\nThis is
- a full RESTful API linked to an extensive database detailing everything about
- the Pokémon main game series.\n\nWe've covered everything from Pokémon to Berry
- Flavors.\n\n## Where do I start?\n\nWe have awesome [documentation](https://pokeapi.co/docs/v2)
- on how to use this API. It takes minutes to get started.\n\nThis API will always
- be publicly available and will never require any extensive setup process to consume.\n\nCreated
- by [**Paul Hallett**](https://github.com/phalt) and other [**PokéAPI contributors**](https://github.com/PokeAPI/pokeapi#contributing)
- around the world. Pokémon and Pokémon character names are trademarks of Nintendo.\n
- \ "
+ description: |-
+ All the Pokémon data you'll ever need in one place, easily accessible through a modern free open-source RESTful API.
+
+ ## What is this?
+
+ This is a full RESTful API linked to an extensive database detailing everything about the Pokémon main game series.
+
+ We've covered everything from Pokémon to Berry Flavors.
+
+ ## Where do I start?
+
+ We have awesome [documentation](https://pokeapi.co/docs/v2) on how to use this API. It takes minutes to get started.
+
+ This API will always be publicly available and will never require any extensive setup process to consume.
+
+ Created by [**Paul Hallett**](https://github.com/phalt) and other [**PokéAPI contributors**](https://github.com/PokeAPI/pokeapi#contributing) around the world. Pokémon and Pokémon character names are trademarks of Nintendo.
paths:
/api/v2/meta/:
get:
@@ -28,17 +34,7 @@ paths:
content:
application/json:
schema:
- type: object
- properties:
- deploy_date:
- type: string
- nullable: true
- hash:
- type: string
- nullable: true
- tag:
- type: string
- nullable: true
+ $ref: '#/components/schemas/PokeapiMetaResponse'
description: ''
/api/v2/ability/:
get:
@@ -3038,7 +3034,7 @@ paths:
description: ''
/api/v2/pokemon/{pokemon_id}/encounters:
get:
- operationId: pokemon_encounters_retrieve
+ operationId: pokemon_encounters_list
description: Handles Pokemon Encounters as a sub-resource.
summary: Get pokemon encounter
parameters:
@@ -3059,97 +3055,7 @@ paths:
schema:
type: array
items:
- type: object
- required:
- - location_area
- - version_details
- properties:
- location_area:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- example: cerulean-city-area
- url:
- type: string
- format: uri
- example: https://pokeapi.co/api/v2/location-area/281/
- version_details:
- type: array
- items:
- type: object
- required:
- - encounter_details
- - max_chance
- - version
- properties:
- encounter_details:
- type: array
- items:
- type: object
- required:
- - chance
- - condition_values
- - max_level
- - method
- - min_level
- properties:
- chance:
- type: number
- example: 100
- condition_values:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- example: story-progress-beat-red
- url:
- type: string
- format: uri
- example: https://pokeapi.co/api/v2/encounter-condition-value/55/
- max_level:
- type: number
- example: 10
- method:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- example: gift
- url:
- type: string
- format: uri
- example: https://pokeapi.co/api/v2/encounter-method/18/
- min_level:
- type: number
- example: 10
- max_chance:
- type: number
- example: 100
- version:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- example: red
- url:
- type: string
- format: uri
- example: https://pokeapi.co/api/v2/version/1/
+ $ref: '#/components/schemas/PokemonEncounterResponse'
description: ''
components:
schemas:
@@ -3213,34 +3119,7 @@ components:
pokemon:
type: array
items:
- type: object
- required:
- - is_hidden
- - slot
- - pokemon
- properties:
- is_hidden:
- type: boolean
- slot:
- type: integer
- format: int32
- examples:
- - 3
- pokemon:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - gloom
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon/44/
+ $ref: '#/components/schemas/AbilityPokemonDetail'
readOnly: true
required:
- effect_changes
@@ -3290,6 +3169,20 @@ components:
required:
- language
- name
+ AbilityPokemonDetail:
+ type: object
+ properties:
+ is_hidden:
+ type: boolean
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ pokemon:
+ $ref: '#/components/schemas/PokemonSummary'
+ required:
+ - pokemon
+ - slot
AbilitySummary:
type: object
properties:
@@ -3313,27 +3206,39 @@ components:
type: string
maxLength: 200
growth_time:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
max_harvest:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
natural_gift_power:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
size:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
smoothness:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
soil_dryness:
- type: integer
+ type:
+ - integer
+ - 'null'
maximum: 2147483647
minimum: -2147483648
firmness:
@@ -3341,32 +3246,7 @@ components:
flavors:
type: array
items:
- type: object
- required:
- - potency
- - flavor
- properties:
- potency:
- type: integer
- examples:
- - 10
- flavor:
- type: object
- require:
- - name
- - url
- properties:
- name:
- type: string
- description: The name of the flavor
- examples:
- - spicy
- url:
- type: string
- format: uri
- description: The URL to get more information about the flavor
- examples:
- - https://pokeapi.co/api/v2/berry-flavor/1/
+ $ref: '#/components/schemas/BerryFlavorMap'
readOnly: true
item:
$ref: '#/components/schemas/ItemSummary'
@@ -3427,6 +3307,18 @@ components:
required:
- name
- url
+ BerryFlavorBerryMap:
+ type: object
+ properties:
+ potency:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ berry:
+ $ref: '#/components/schemas/BerrySummary'
+ required:
+ - berry
+ - potency
BerryFlavorDetail:
type: object
properties:
@@ -3439,32 +3331,7 @@ components:
berries:
type: array
items:
- type: object
- required:
- - potency
- - berry
- properties:
- potency:
- type: integer
- examples:
- - 10
- berry:
- type: object
- require:
- - name
- - url
- properties:
- name:
- type: string
- description: The name of the berry
- examples:
- - rowap
- url:
- type: string
- format: uri
- description: The URL to get more information about the berry
- examples:
- - https://pokeapi.co/api/v2/berry/64/
+ $ref: '#/components/schemas/BerryFlavorBerryMap'
readOnly: true
contest_type:
$ref: '#/components/schemas/ContestTypeSummary'
@@ -3479,6 +3346,18 @@ components:
- id
- name
- names
+ BerryFlavorMap:
+ type: object
+ properties:
+ potency:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ flavor:
+ $ref: '#/components/schemas/BerryFlavorSummary'
+ required:
+ - flavor
+ - potency
BerryFlavorName:
type: object
properties:
@@ -3538,15 +3417,6 @@ components:
type: array
items:
type: integer
- format: int32
- examples:
- - - 0
- - 5
- - 10
- - 15
- - 20
- - 25
- - 30
readOnly: true
highest_stat:
$ref: '#/components/schemas/StatSummary'
@@ -3741,22 +3611,7 @@ components:
pokemon_species:
type: array
items:
- type: object
- required:
- - potency
- - flavor
- properties:
- name:
- type: string
- description: Pokemon species name.
- examples:
- - bulbasaur
- url:
- type: string
- format: uri
- description: The URL to get more information about the species
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/1/
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
readOnly: true
required:
- id
@@ -3928,6 +3783,21 @@ components:
required:
- name
- url
+ EncounterPokemonDetail:
+ type: object
+ properties:
+ min_perfect_ivs:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ always_shiny:
+ type: boolean
+ never_shiny:
+ type: boolean
+ is_alpha:
+ type: boolean
EvolutionChainDetail:
type: object
properties:
@@ -3937,288 +3807,34 @@ components:
baby_trigger_item:
$ref: '#/components/schemas/ItemSummary'
chain:
- type: object
- required:
- - evolution_details
- - evolves_to
- - is_baby
- - species
- properties:
- evolution_details:
- type: array
- items: {}
- examples: []
- evolves_to:
- type: array
- items:
- type: object
- required:
- - evolution_details
- - evolves_to
- - is_baby
- - species
- properties:
- evolution_details:
- type: array
- items:
- type: object
- required:
- - version_group
- - is_default
- - gender
- - held_item
- - item
- - known_move
- - known_move_type
- - location
- - min_affection
- - min_beauty
- - min_damage_taken
- - min_happiness
- - min_level
- - min_move_count
- - min_steps
- - near_special_rock
- - needs_multiplayer
- - needs_overworld_rain
- - party_species
- - party_type
- - relative_physical_stats
- - time_of_day
- - trade_species
- - trigger
- - turn_upside_down
- - used_move
- - region
- - base_form
- - evolved_form
- properties:
- version_group:
- type: object
- nullable: false
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - 1
- url:
- type: string
- format: uri
- examples:
- - 2
- is_default:
- type: boolean
- gender:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - 1
- url:
- type: string
- format: uri
- examples:
- - 2
- held_item:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - 1
- url:
- type: string
- format: uri
- examples:
- - 2
- item:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - 1
- url:
- type: string
- format: uri
- examples:
- - 2
- known_move:
- type: ''
- nullable: true
- known_move_type:
- type: ''
- nullable: true
- location:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- url:
- type: string
- format: uri
- min_affection:
- type: integer
- format: int32
- nullable: true
- min_beauty:
- type: integer
- format: int32
- nullable: true
- min_damage_taken:
- type: integer
- format: int32
- nullable: true
- min_happiness:
- type: integer
- format: int32
- nullable: true
- min_level:
- type: integer
- format: int32
- nullable: true
- min_move_count:
- type: integer
- format: int32
- nullable: true
- min_steps:
- type: integer
- format: int32
- nullable: true
- near_special_rock:
- type: boolean
- nullable: true
- needs_multiplayer:
- type: boolean
- nullable: true
- needs_overworld_rain:
- type: boolean
- nullable: true
- party_species:
- type: string
- nullable: true
- party_type:
- type: string
- nullable: true
- relative_physical_stats:
- type: string
- nullable: true
- time_of_day:
- type: string
- trade_species:
- type: string
- nullable: true
- trigger:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- url:
- type: string
- format: uri
- turn_upside_down:
- type: boolean
- used_move:
- type: ''
- nullable: true
- region:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- url:
- type: string
- format: uri
- base_form:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- url:
- type: string
- format: uri
- evolved_form:
- type: object
- nullable: true
- required:
- - name
- - url
- properties:
- name:
- type: string
- url:
- type: string
- format: uri
- is_baby:
- type: boolean
- species:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - happiny
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/440/
- is_baby:
- type: boolean
- species:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - happiny
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/440/
+ allOf:
+ - $ref: '#/components/schemas/EvolutionChainLink'
readOnly: true
required:
- baby_trigger_item
- chain
- id
+ EvolutionChainLink:
+ type: object
+ properties:
+ is_baby:
+ type: boolean
+ species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ evolution_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonEvolution'
+ evolves_to:
+ type: array
+ items:
+ type: object
+ additionalProperties: {}
+ required:
+ - evolution_details
+ - evolves_to
+ - is_baby
+ - species
EvolutionChainSummary:
type: object
properties:
@@ -4245,20 +3861,7 @@ components:
pokemon_species:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - ivysaur
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/2/
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
readOnly: true
required:
- id
@@ -4315,55 +3918,28 @@ components:
pokemon_species_details:
type: array
items:
- type: object
- required:
- - rate
- - pokemon_species
- properties:
- rate:
- type: integer
- format: int32
- examples:
- - 1
- pokemon_species:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bulbasaur
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/1/
+ $ref: '#/components/schemas/GenderPokemonSpecies'
readOnly: true
required_for_evolution:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - wormadam
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/413/
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
readOnly: true
required:
- id
- name
- pokemon_species_details
- required_for_evolution
+ GenderPokemonSpecies:
+ type: object
+ properties:
+ rate:
+ type: integer
+ pokemon_species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ required:
+ - pokemon_species
+ - rate
GenderSummary:
type: object
properties:
@@ -4536,20 +4112,7 @@ components:
items:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - master-ball
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/item/1/
+ $ref: '#/components/schemas/ItemSummary'
readOnly: true
names:
type: array
@@ -4657,20 +4220,7 @@ components:
attributes:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - countable
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/item-attribute/1/
+ $ref: '#/components/schemas/ItemAttributeSummary'
readOnly: true
category:
$ref: '#/components/schemas/ItemCategorySummary'
@@ -4702,105 +4252,21 @@ components:
held_by_pokemon:
type: array
items:
- type: object
- required:
- - pokemon
- - version-details
- properties:
- pokemon:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - farfetchd
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon/83/
- version-details:
- type: array
- items:
- type: object
- required:
- - rarity
- - version
- properties:
- rarity:
- type: integer
- format: int32
- examples:
- - 5
- version:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - ruby
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version/7/
+ $ref: '#/components/schemas/PokemonHeldItem'
readOnly: true
sprites:
- type: object
- required:
- - default
- properties:
- default:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/media/sprites/items/master-ball.png
+ allOf:
+ - $ref: '#/components/schemas/ItemSprites'
readOnly: true
baby_trigger_for:
- type: object
- required:
- - url
- properties:
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/evolution-chain/51/
+ oneOf:
+ - $ref: '#/components/schemas/EvolutionChainSummary'
+ - type: 'null'
readOnly: true
machines:
type: array
items:
- type: object
- required:
- - machine
- - version_group
- properties:
- machine:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/machine/1/
- version_group:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - sword-shield
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/20/
+ $ref: '#/components/schemas/ItemMachine'
readOnly: true
required:
- attributes
@@ -4905,6 +4371,16 @@ components:
required:
- game_index
- generation
+ ItemMachine:
+ type: object
+ properties:
+ machine:
+ $ref: '#/components/schemas/MachineSummary'
+ version_group:
+ $ref: '#/components/schemas/VersionGroupSummary'
+ required:
+ - machine
+ - version_group
ItemName:
type: object
properties:
@@ -4986,6 +4462,15 @@ components:
required:
- currency
- version_group
+ ItemSprites:
+ type: object
+ properties:
+ default:
+ type:
+ - string
+ - 'null'
+ required:
+ - default
ItemSummary:
type: object
properties:
@@ -5067,54 +4552,7 @@ components:
encounter_method_rates:
type: array
items:
- type: object
- required:
- - encounter_method
- - version_details
- properties:
- encounter_method:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - old-rod
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/encounter-method/2/
- version_details:
- type: array
- items:
- type: object
- required:
- - rate
- - version
- properties:
- rate:
- type: integer
- format: int32
- examples:
- - 5
- version:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - platinum
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version/14/
+ $ref: '#/components/schemas/LocationAreaEncounterRate'
readOnly: true
location:
$ref: '#/components/schemas/LocationSummary'
@@ -5126,109 +4564,7 @@ components:
pokemon_encounters:
type: array
items:
- type: object
- required:
- - pokemon
- - version_details
- properties:
- pokemon:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - tentacool
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon/72/
- version_details:
- type: array
- items:
- type: object
- required:
- - version
- - max_chance
- - encounter_details
- properties:
- version:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - diamond
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version/12/
- max_chance:
- type: integer
- format: int32
- examples:
- - 60
- encounter_details:
- type: object
- required:
- - min_level
- - max_level
- - condition_value
- - chance
- - method
- properties:
- min_level:
- type: integer
- format: int32
- examples:
- - 20
- max_level:
- type: integer
- format: int32
- examples:
- - 30
- condition_values:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - slot2-sapphire
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/encounter-condition-value/10/
- chance:
- type: integer
- format: int32
- examples:
- - 60
- method:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - surf
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/encounter-method/5/
+ $ref: '#/components/schemas/LocationAreaPokemonEncounter'
readOnly: true
required:
- encounter_method_rates
@@ -5238,6 +4574,55 @@ components:
- name
- names
- pokemon_encounters
+ LocationAreaEncounterDetail:
+ type: object
+ properties:
+ min_level:
+ type: integer
+ max_level:
+ type: integer
+ chance:
+ type: integer
+ default: 0
+ method:
+ $ref: '#/components/schemas/EncounterMethodSummary'
+ condition_values:
+ type: array
+ items:
+ $ref: '#/components/schemas/EncounterConditionValueSummary'
+ readOnly: true
+ pokemon_details:
+ oneOf:
+ - $ref: '#/components/schemas/EncounterPokemonDetail'
+ - type: 'null'
+ readOnly: true
+ required:
+ - condition_values
+ - max_level
+ - min_level
+ - pokemon_details
+ LocationAreaEncounterRate:
+ type: object
+ properties:
+ encounter_method:
+ $ref: '#/components/schemas/EncounterMethodSummary'
+ version_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/LocationAreaEncounterVersionDetail'
+ required:
+ - encounter_method
+ - version_details
+ LocationAreaEncounterVersionDetail:
+ type: object
+ properties:
+ rate:
+ type: integer
+ version:
+ $ref: '#/components/schemas/VersionSummary'
+ required:
+ - rate
+ - version
LocationAreaName:
type: object
properties:
@@ -5249,6 +4634,33 @@ components:
required:
- language
- name
+ LocationAreaPokemonEncounter:
+ type: object
+ properties:
+ pokemon:
+ $ref: '#/components/schemas/PokemonSummary'
+ version_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/LocationAreaPokemonEncounterVersion'
+ required:
+ - pokemon
+ - version_details
+ LocationAreaPokemonEncounterVersion:
+ type: object
+ properties:
+ version:
+ $ref: '#/components/schemas/VersionSummary'
+ max_chance:
+ type: integer
+ encounter_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/LocationAreaEncounterDetail'
+ required:
+ - encounter_details
+ - max_chance
+ - version
LocationAreaSummary:
type: object
properties:
@@ -5425,35 +4837,7 @@ components:
effect_entries:
type: array
items:
- type: object
- required:
- - effect
- - short_effect
- - language
- properties:
- effect:
- type: string
- examples:
- - Inflicts regular damage.
- short_effect:
- type: string
- examples:
- - Inflicts regular damage with no additional effect.
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
+ $ref: '#/components/schemas/MoveEffectEffectText'
readOnly: true
type:
$ref: '#/components/schemas/TypeSummary'
@@ -5464,6 +4848,34 @@ components:
- effect_entries
- type
- version_group
+ MoveComboUsage:
+ type: object
+ properties:
+ use_before:
+ type:
+ - array
+ - 'null'
+ items:
+ $ref: '#/components/schemas/MoveSummary'
+ use_after:
+ type:
+ - array
+ - 'null'
+ items:
+ $ref: '#/components/schemas/MoveSummary'
+ required:
+ - use_after
+ - use_before
+ MoveCombos:
+ type: object
+ properties:
+ normal:
+ $ref: '#/components/schemas/MoveComboUsage'
+ super:
+ $ref: '#/components/schemas/MoveComboUsage'
+ required:
+ - normal
+ - super
MoveDamageClassDescription:
type: object
properties:
@@ -5564,95 +4976,9 @@ components:
maximum: 2147483647
minimum: -2147483648
contest_combos:
- type: object
- required:
- - normal
- - super
- properties:
- normal:
- type: object
- required:
- - use_before
- - use_after
- properties:
- use_before:
- type: array
- nullable: true
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - fire-punch
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/7/
- use_after:
- type: array
- nullable: true
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - ice-punch
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/8/
- super:
- type: object
- required:
- - use_before
- - use_after
- properties:
- use_before:
- type: array
- nullable: true
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - night-slash
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/400/
- use_after:
- type: array
- nullable: true
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - focus-energy
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/116/
+ oneOf:
+ - $ref: '#/components/schemas/MoveCombos'
+ - type: 'null'
readOnly: true
contest_type:
$ref: '#/components/schemas/ContestTypeSummary'
@@ -5663,86 +4989,12 @@ components:
effect_entries:
type: array
items:
- type: object
- required:
- - effect
- - short_effect
- - language
- properties:
- effect:
- type: string
- examples:
- - Inflicts regular damage.
- short_effect:
- type: string
- examples:
- - Inflicts regular damage with no additional effect.
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
+ $ref: '#/components/schemas/MoveEffectEffectText'
readOnly: true
effect_changes:
type: array
items:
- type: object
- required:
- - effect_entries
- - version_group
- properties:
- effect_entries:
- type: array
- items:
- type: object
- required:
- - effect
- - language
- properties:
- effect:
- type: string
- examples:
- - Hits Pokémon under the effects of dig and fly.
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
- version_group:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - gold-silver
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/3/
+ $ref: '#/components/schemas/MoveEffectChange'
readOnly: true
generation:
$ref: '#/components/schemas/GenerationSummary'
@@ -5763,31 +5015,7 @@ components:
stat_changes:
type: array
items:
- type: object
- required:
- - change
- - stat
- properties:
- change:
- type: integer
- format: int32
- examples:
- - 2
- stat:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - attack
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/stat/1/
+ $ref: '#/components/schemas/MoveMetaStatChange'
readOnly: true
super_contest_effect:
$ref: '#/components/schemas/SuperContestEffectSummary'
@@ -5798,36 +5026,7 @@ components:
machines:
type: array
items:
- type: object
- required:
- - machine
- - version_group
- properties:
- machine:
- type: object
- required:
- - url
- properties:
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/machine/1/
- version_group:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - sword-shield
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/1/
+ $ref: '#/components/schemas/ItemMachine'
readOnly: true
flavor_text_entries:
type: array
@@ -5837,20 +5036,7 @@ components:
learned_by_pokemon:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - clefairy
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon/35/
+ $ref: '#/components/schemas/PokemonSummary'
readOnly: true
required:
- contest_combos
@@ -5873,6 +5059,45 @@ components:
- super_contest_effect
- target
- type
+ MoveEffectChange:
+ type: object
+ properties:
+ version_group:
+ $ref: '#/components/schemas/VersionGroupSummary'
+ effect_entries:
+ type: array
+ items:
+ $ref: '#/components/schemas/MoveEffectChangeEffectText'
+ readOnly: true
+ required:
+ - effect_entries
+ - version_group
+ MoveEffectChangeEffectText:
+ type: object
+ properties:
+ effect:
+ type: string
+ maxLength: 6000
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - effect
+ - language
+ MoveEffectEffectText:
+ type: object
+ properties:
+ effect:
+ type: string
+ maxLength: 6000
+ short_effect:
+ type: string
+ maxLength: 300
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - effect
+ - language
+ - short_effect
MoveFlavorText:
type: object
properties:
@@ -5918,20 +5143,7 @@ components:
version_groups:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - red-blue
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/1/
+ $ref: '#/components/schemas/VersionGroupSummary'
readOnly: true
required:
- descriptions
@@ -6045,20 +5257,7 @@ components:
moves:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - thunder-punch
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/9/
+ $ref: '#/components/schemas/MoveSummary'
readOnly: true
names:
type: array
@@ -6121,20 +5320,7 @@ components:
moves:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - sing
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/47/
+ $ref: '#/components/schemas/MoveSummary'
readOnly: true
required:
- descriptions
@@ -6154,6 +5340,18 @@ components:
required:
- name
- url
+ MoveMetaStatChange:
+ type: object
+ properties:
+ change:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ stat:
+ $ref: '#/components/schemas/StatSummary'
+ required:
+ - change
+ - stat
MoveName:
type: object
properties:
@@ -6165,6 +5363,18 @@ components:
required:
- language
- name
+ MoveStatChange:
+ type: object
+ properties:
+ change:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ move:
+ $ref: '#/components/schemas/MoveSummary'
+ required:
+ - change
+ - move
MoveSummary:
type: object
properties:
@@ -6284,31 +5494,7 @@ components:
pokeathlon_stat_changes:
type: array
items:
- type: object
- required:
- - max_change
- - pokeathlon_stat
- properties:
- max_change:
- type: integer
- format: int32
- examples:
- - 1
- pokeathlon_stat:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - power
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokeathlon-stat/2/
+ $ref: '#/components/schemas/NaturePokeathlonStat'
readOnly: true
move_battle_style_preferences:
type: array
@@ -6342,6 +5528,18 @@ components:
required:
- language
- name
+ NaturePokeathlonStat:
+ type: object
+ properties:
+ max_change:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ pokeathlon_stat:
+ $ref: '#/components/schemas/PokeathlonStatSummary'
+ required:
+ - max_change
+ - pokeathlon_stat
NatureSummary:
type: object
properties:
@@ -7499,37 +6697,7 @@ components:
pokemon_encounters:
type: array
items:
- type: object
- required:
- - base_score
- - pokemon-species
- - rate
- properties:
- base_score:
- type: integer
- format: int32
- examples:
- - 50
- pokemon-species:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bulbasaur
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/1/
- rate:
- type: integer
- format: int32
- examples:
- - 30
+ $ref: '#/components/schemas/PalParkEncounter'
readOnly: true
required:
- id
@@ -7560,79 +6728,81 @@ components:
required:
- name
- url
- PokeathlonStatDetail:
+ PalParkEncounter:
type: object
properties:
- id:
- type: integer
- readOnly: true
- name:
- type: string
+ base_score:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ rate:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ pokemon_species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ required:
+ - pokemon_species
+ - rate
+ PokeapiMetaResponse:
+ type: object
+ properties:
+ deploy_date:
+ type:
+ - string
+ - 'null'
+ hash:
+ type:
+ - string
+ - 'null'
+ tag:
+ type:
+ - string
+ - 'null'
+ required:
+ - deploy_date
+ - hash
+ - tag
+ PokeathlonStatAffectingNature:
+ type: object
+ properties:
+ max_change:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ nature:
+ $ref: '#/components/schemas/NatureSummary'
+ required:
+ - max_change
+ - nature
+ PokeathlonStatAffectingNatures:
+ type: object
+ properties:
+ increase:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokeathlonStatAffectingNature'
+ decrease:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokeathlonStatAffectingNature'
+ required:
+ - decrease
+ - increase
+ PokeathlonStatDetail:
+ type: object
+ properties:
+ id:
+ type: integer
+ readOnly: true
+ name:
+ type: string
maxLength: 200
affecting_natures:
- type: object
- required:
- - decrease
- - increase
- properties:
- decrease:
- type: array
- items:
- type: object
- required:
- - max_change
- - nature
- properties:
- max_change:
- type: integer
- format: int32
- maximum: -1
- examples:
- - -1
- nature:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - hardy
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/nature/1/
- increase:
- type: array
- items:
- type: object
- required:
- - max_change
- - nature
- properties:
- max_change:
- type: integer
- format: int32
- minimum: 1
- examples:
- - 2
- nature:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - hardy
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/nature/1/
+ allOf:
+ - $ref: '#/components/schemas/PokeathlonStatAffectingNatures'
readOnly: true
names:
type: array
@@ -7702,51 +6872,14 @@ components:
pokemon_entries:
type: array
items:
- type: object
- required:
- - entry_number
- - pokemon_species
- properties:
- entry_number:
- type: integer
- format: int32
- examples:
- - 1
- pokemon_species:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bulbasaur
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-species/1/
+ $ref: '#/components/schemas/PokemonDexNumber'
readOnly: true
region:
$ref: '#/components/schemas/RegionSummary'
version_groups:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - the-teal-mask
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/26/
+ $ref: '#/components/schemas/VersionGroupSummary'
readOnly: true
required:
- descriptions
@@ -7780,6 +6913,34 @@ components:
required:
- name
- url
+ PokemonAbility:
+ type: object
+ properties:
+ is_hidden:
+ type: boolean
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ ability:
+ $ref: '#/components/schemas/AbilitySummary'
+ required:
+ - ability
+ - slot
+ PokemonAbilityPast:
+ type: object
+ properties:
+ is_hidden:
+ type: boolean
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ ability:
+ $ref: '#/components/schemas/AbilitySummary'
+ required:
+ - ability
+ - slot
PokemonColorDetail:
type: object
properties:
@@ -7828,6 +6989,20 @@ components:
required:
- name
- url
+ PokemonCries:
+ type: object
+ properties:
+ latest:
+ type:
+ - string
+ - 'null'
+ legacy:
+ type:
+ - string
+ - 'null'
+ required:
+ - latest
+ - legacy
PokemonDetail:
type: object
properties:
@@ -7866,89 +7041,12 @@ components:
abilities:
type: array
items:
- type: object
- required:
- - ability
- - is_hidden
- - slot
- properties:
- ability:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - sand-veil
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/ability/8/
- is_hidden:
- type: boolean
- slot:
- type: integer
- format: int32
- examples:
- - 1
+ $ref: '#/components/schemas/PokemonAbility'
readOnly: true
past_abilities:
type: array
items:
- type: object
- required:
- - abilities
- - generation
- properties:
- abilities:
- type: array
- items:
- type: object
- required:
- - ability
- - is_hidden
- - slot
- properties:
- ability:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - levitate
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/ability/26/
- is_hidden:
- type: boolean
- slot:
- type: integer
- format: int32
- examples:
- - 1
- generation:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - generation-vi
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/generation/6/
+ $ref: '#/components/schemas/PokemonPastAbility'
readOnly: true
forms:
type: array
@@ -7963,169 +7061,25 @@ components:
held_items:
type: array
items:
- type: object
- required:
- - item
- - version_details
- properties:
- item:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - soft-sand
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/item/214/
- version_details:
- type: array
- items:
- type: object
- required:
- - rarity
- - version
- properties:
- rarity:
- type: integer
- format: int32
- examples:
- - 5
- version:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - diamond
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version/12/
+ $ref: '#/components/schemas/PokemonHeldItem'
readOnly: true
location_area_encounters:
type: string
- examples:
- - https://pokeapi.co/api/v2/pokemon/1/encounters
readOnly: true
moves:
type: array
items:
- type: object
- required:
- - move
- - version_group_details
- properties:
- move:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - scratch
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/10/
- version_group_details:
- type: array
- items:
- type: object
- required:
- - level_learned_at
- - move_learn_method
- - version_group
- properties:
- level_learned_at:
- type: integer
- format: int32
- examples:
- - 1
- move_learn_method:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - level-up
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move-learn-method/1/
- version_group:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - red-blue
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/1/
+ $ref: '#/components/schemas/PokemonMove'
readOnly: true
species:
$ref: '#/components/schemas/PokemonSpeciesSummary'
sprites:
- type: object
- properties:
- front_default:
- type: string
- format: uri
- exmaple: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png
- additionalProperties:
- type: string
- format: uri
- nullable: true
- examples:
- - https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png
- examples:
- - back_default: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png
- back_female: null
- back_shiny: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png
- back_shiny_female: null
- front_default: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png
- front_female: null
- front_shiny: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png
- front_shiny_female: null
+ allOf:
+ - $ref: '#/components/schemas/PokemonSprites'
readOnly: true
cries:
- type: object
- required:
- - latest
- - legacy
- properties:
- latest:
- type: string
- format: uri
- examples:
- - https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/50.ogg
- legacy:
- type: string
- format: uri
- examples:
- - https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/legacy/50.ogg
+ allOf:
+ - $ref: '#/components/schemas/PokemonCries'
readOnly: true
stats:
type: array
@@ -8135,141 +7089,17 @@ components:
past_stats:
type: array
items:
- type: object
- required:
- - generation
- - stats
- properties:
- generation:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - generation-vi
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/generation/6/
- stats:
- type: array
- items:
- type: object
- required:
- - base_stat
- - effort
- - stat
- properties:
- base_stat:
- type: integer
- format: int32
- examples:
- - 45
- effort:
- type: integer
- format: int32
- examples:
- - 0
- stat:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - speed
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/stat/6/
+ $ref: '#/components/schemas/PokemonPastStat'
readOnly: true
types:
type: array
items:
- type: object
- required:
- - slot
- - type
- properties:
- slot:
- type: integer
- format: int32
- examples:
- - 1
- type:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - ghost
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/8/
+ $ref: '#/components/schemas/PokemonType'
readOnly: true
past_types:
type: array
items:
- type: object
- required:
- - generation
- - types
- properties:
- generation:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - generation-v
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/generation/5/
- types:
- type: array
- items:
- type: object
- required:
- - slot
- - type
- properties:
- slot:
- type: integer
- format: int32
- examples:
- - 1
- type:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - normal
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/1/
+ $ref: '#/components/schemas/PokemonPastType'
readOnly: true
required:
- abilities
@@ -8298,6 +7128,175 @@ components:
required:
- entry_number
- pokedex
+ PokemonDexNumber:
+ type: object
+ properties:
+ entry_number:
+ type: integer
+ pokemon_species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ required:
+ - entry_number
+ - pokemon_species
+ PokemonEncounterDetailResponse:
+ type: object
+ properties:
+ chance:
+ type: integer
+ condition_values:
+ type: array
+ items:
+ $ref: '#/components/schemas/EncounterConditionValueSummary'
+ max_level:
+ type: integer
+ method:
+ $ref: '#/components/schemas/EncounterMethodSummary'
+ min_level:
+ type: integer
+ required:
+ - chance
+ - condition_values
+ - max_level
+ - method
+ - min_level
+ PokemonEncounterResponse:
+ type: object
+ properties:
+ location_area:
+ $ref: '#/components/schemas/LocationAreaSummary'
+ version_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonEncounterVersionDetailResponse'
+ required:
+ - location_area
+ - version_details
+ PokemonEncounterVersionDetailResponse:
+ type: object
+ properties:
+ version:
+ $ref: '#/components/schemas/VersionSummary'
+ max_chance:
+ type: integer
+ encounter_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonEncounterDetailResponse'
+ required:
+ - encounter_details
+ - max_chance
+ - version
+ PokemonEvolution:
+ type: object
+ properties:
+ version_group:
+ $ref: '#/components/schemas/VersionGroupSummary'
+ is_default:
+ type: boolean
+ item:
+ $ref: '#/components/schemas/ItemSummary'
+ trigger:
+ $ref: '#/components/schemas/EvolutionTriggerSummary'
+ gender:
+ type:
+ - integer
+ - 'null'
+ held_item:
+ $ref: '#/components/schemas/ItemSummary'
+ known_move:
+ $ref: '#/components/schemas/MoveSummary'
+ known_move_type:
+ $ref: '#/components/schemas/TypeSummary'
+ location:
+ $ref: '#/components/schemas/LocationSummary'
+ min_level:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ min_happiness:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ min_beauty:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ min_affection:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ near_special_rock:
+ type: boolean
+ needs_multiplayer:
+ type: boolean
+ needs_overworld_rain:
+ type: boolean
+ party_species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ party_type:
+ $ref: '#/components/schemas/TypeSummary'
+ relative_physical_stats:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ time_of_day:
+ type: string
+ maxLength: 10
+ trade_species:
+ $ref: '#/components/schemas/PokemonSpeciesSummary'
+ turn_upside_down:
+ type: boolean
+ region:
+ $ref: '#/components/schemas/RegionSummary'
+ base_form:
+ $ref: '#/components/schemas/PokemonSummary'
+ evolved_form:
+ $ref: '#/components/schemas/PokemonSummary'
+ used_move:
+ $ref: '#/components/schemas/MoveSummary'
+ min_move_count:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ min_steps:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ min_damage_taken:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ required:
+ - base_form
+ - evolved_form
+ - held_item
+ - item
+ - known_move
+ - known_move_type
+ - location
+ - party_species
+ - party_type
+ - region
+ - trade_species
+ - trigger
+ - used_move
+ - version_group
PokemonFormDetail:
type: object
properties:
@@ -8331,151 +7330,30 @@ components:
pokemon:
$ref: '#/components/schemas/PokemonSummary'
sprites:
- type: object
- properties:
- default:
- type: string
- format: uri
- examples:
- - https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/412.png
- additionalProperties:
- type: string
- format: uri
- nullable: true
- examples:
- - https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/412.png
- examples:
- - back_default: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/412.png
- back_female: null
- back_shiny: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/412.png
- back_shiny_female: null
- front_default: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/412.png
- front_female: null
- front_shiny: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/412.png
- front_shiny_female: null
+ allOf:
+ - $ref: '#/components/schemas/PokemonFormSprites'
readOnly: true
version_group:
$ref: '#/components/schemas/VersionGroupSummary'
form_names:
type: array
items:
- type: object
- required:
- - language
- - name
- properties:
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
- name:
- type: string
- examples:
- - Plant Cloak
+ $ref: '#/components/schemas/PokemonFormName'
readOnly: true
names:
type: array
items:
- type: object
- required:
- - language
- - name
- properties:
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
- name:
- type: string
- examples:
- - Plant Cloak
+ $ref: '#/components/schemas/PokemonFormName'
readOnly: true
types:
type: array
items:
- type: object
- required:
- - slot
- - type
- properties:
- slot:
- type: integer
- format: int32
- examples:
- - 1
- type:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bug
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/7/
+ $ref: '#/components/schemas/PokemonFormType'
readOnly: true
trigger_conditions:
type: array
items:
- type: object
- required:
- - trigger
- - name
- - url
- properties:
- trigger:
- type: string
- examples:
- - held-item
- name:
- type: string
- examples:
- - venusaurite
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/item/698/
- base_form:
- type: object
- nullable: true
- properties:
- name:
- type: string
- examples:
- - necrozma-dusk
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon-form/10314/
+ $ref: '#/components/schemas/PokemonFormTriggerCondition'
readOnly: true
required:
- form_name
@@ -8488,6 +7366,23 @@ components:
- trigger_conditions
- types
- version_group
+ PokemonFormName:
+ type: object
+ properties:
+ name:
+ type: string
+ maxLength: 200
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - language
+ - name
+ PokemonFormSprites:
+ type: object
+ properties:
+ sprites: {}
+ required:
+ - sprites
PokemonFormSummary:
type: object
properties:
@@ -8501,6 +7396,28 @@ components:
required:
- name
- url
+ PokemonFormTriggerCondition:
+ type: object
+ properties:
+ trigger:
+ type: string
+ base_form:
+ $ref: '#/components/schemas/PokemonFormSummary'
+ required:
+ - base_form
+ - trigger
+ PokemonFormType:
+ type: object
+ properties:
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ type:
+ $ref: '#/components/schemas/TypeSummary'
+ required:
+ - slot
+ - type
PokemonGameIndex:
type: object
properties:
@@ -8561,6 +7478,102 @@ components:
required:
- name
- url
+ PokemonHeldItem:
+ type: object
+ properties:
+ pokemon:
+ $ref: '#/components/schemas/PokemonSummary'
+ version_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonHeldItemVersion'
+ required:
+ - pokemon
+ - version_details
+ PokemonHeldItemVersion:
+ type: object
+ properties:
+ rarity:
+ type: integer
+ version:
+ $ref: '#/components/schemas/VersionSummary'
+ required:
+ - rarity
+ - version
+ PokemonMove:
+ type: object
+ properties:
+ move:
+ $ref: '#/components/schemas/MoveSummary'
+ version_group_details:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonMoveVersionGroup'
+ required:
+ - move
+ - version_group_details
+ PokemonMoveVersionGroup:
+ type: object
+ properties:
+ level_learned_at:
+ type: integer
+ move_learn_method:
+ $ref: '#/components/schemas/MoveLearnMethodSummary'
+ version_group:
+ $ref: '#/components/schemas/VersionGroupSummary'
+ order:
+ type: integer
+ required:
+ - level_learned_at
+ - move_learn_method
+ - version_group
+ PokemonPastAbility:
+ type: object
+ properties:
+ abilities:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonAbilityPast'
+ generation:
+ $ref: '#/components/schemas/GenerationSummary'
+ required:
+ - abilities
+ - generation
+ PokemonPastStat:
+ type: object
+ properties:
+ generation:
+ $ref: '#/components/schemas/GenerationSummary'
+ stats:
+ type: array
+ items:
+ $ref: '#/components/schemas/PokemonStat'
+ required:
+ - generation
+ - stats
+ PokemonPastType:
+ type: object
+ properties:
+ generation:
+ $ref: '#/components/schemas/GenerationSummary'
+ types:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypePokemon'
+ required:
+ - generation
+ - types
+ PokemonShapeAwesomeName:
+ type: object
+ properties:
+ awesome_name:
+ type: string
+ maxLength: 30
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - awesome_name
+ - language
PokemonShapeDetail:
type: object
properties:
@@ -8573,48 +7586,12 @@ components:
awesome_names:
type: array
items:
- type: object
- required:
- - awesome_name
- - language
- properties:
- awesome_name:
- type: string
- examples:
- - Pomaceous
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
+ $ref: '#/components/schemas/PokemonShapeAwesomeName'
readOnly: true
names:
type: array
items:
- type: object
- required:
- - url
- - name
- properties:
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
- name:
- type: string
- examples:
- - Ball
+ $ref: '#/components/schemas/PokemonShapeName'
readOnly: true
pokemon_species:
type: array
@@ -8627,6 +7604,17 @@ components:
- name
- names
- pokemon_species
+ PokemonShapeName:
+ type: object
+ properties:
+ name:
+ type: string
+ maxLength: 200
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - language
+ - name
PokemonShapeSummary:
type: object
properties:
@@ -8709,20 +7697,7 @@ components:
egg_groups:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - monster
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/egg-group/1/
+ $ref: '#/components/schemas/EggGroupSummary'
readOnly: true
color:
$ref: '#/components/schemas/PokemonColorSummary'
@@ -8739,65 +7714,12 @@ components:
names:
type: array
items:
- type: object
- required:
- - language
- - name
- properties:
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
- name:
- type: string
- examples:
- - bulbasaur
+ $ref: '#/components/schemas/PokemonSpeciesName'
readOnly: true
pal_park_encounters:
type: array
items:
- type: object
- required:
- - area
- - base_score
- - rate
- properties:
- area:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - field
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pal-park-area/2/
- base_score:
- type: integer
- format: int32
- examples:
- - 50
- rate:
- type: integer
- format: int32
- examples:
- - 30
+ $ref: '#/components/schemas/PokemonSpeciesPalParkEncounter'
readOnly: true
form_descriptions:
type: array
@@ -8812,56 +7734,12 @@ components:
genera:
type: array
items:
- type: object
- required:
- - genus
- - language
- properties:
- genus:
- type: string
- examples:
- - Seed Pokémon
- language:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - en
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/language/9/
+ $ref: '#/components/schemas/PokemonSpeciesGenus'
readOnly: true
varieties:
type: array
items:
- type: object
- required:
- - is_default
- - pokemon
- properties:
- is_default:
- type: boolean
- pokemon:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bulbasaur
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokemon/1/
+ $ref: '#/components/schemas/PokemonSpeciesVariety'
readOnly: true
required:
- color
@@ -8894,6 +7772,46 @@ components:
- flavor_text
- language
- version
+ PokemonSpeciesGenus:
+ type: object
+ properties:
+ genus:
+ type: string
+ maxLength: 30
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - genus
+ - language
+ PokemonSpeciesName:
+ type: object
+ properties:
+ name:
+ type: string
+ maxLength: 200
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - language
+ - name
+ PokemonSpeciesPalParkEncounter:
+ type: object
+ properties:
+ base_score:
+ type:
+ - integer
+ - 'null'
+ maximum: 2147483647
+ minimum: -2147483648
+ rate:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ area:
+ $ref: '#/components/schemas/PalParkAreaSummary'
+ required:
+ - area
+ - rate
PokemonSpeciesSummary:
type: object
properties:
@@ -8907,6 +7825,25 @@ components:
required:
- name
- url
+ PokemonSpeciesVariety:
+ type: object
+ properties:
+ is_default:
+ type: boolean
+ pokemon:
+ $ref: '#/components/schemas/PokemonSummary'
+ required:
+ - is_default
+ - pokemon
+ PokemonSprites:
+ type: object
+ properties:
+ front_default:
+ type:
+ - string
+ - 'null'
+ required:
+ - front_default
PokemonStat:
type: object
properties:
@@ -8937,6 +7874,18 @@ components:
required:
- name
- url
+ PokemonType:
+ type: object
+ properties:
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ type:
+ $ref: '#/components/schemas/TypeSummary'
+ required:
+ - slot
+ - type
RegionDetail:
type: object
properties:
@@ -8969,20 +7918,7 @@ components:
version_groups:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - red-blue
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/version-group/1/
+ $ref: '#/components/schemas/VersionGroupSummary'
readOnly: true
required:
- id
@@ -9016,6 +7952,34 @@ components:
required:
- name
- url
+ StatAffectingMoves:
+ type: object
+ properties:
+ increase:
+ type: array
+ items:
+ $ref: '#/components/schemas/MoveStatChange'
+ decrease:
+ type: array
+ items:
+ $ref: '#/components/schemas/MoveStatChange'
+ required:
+ - decrease
+ - increase
+ StatAffectingNatures:
+ type: object
+ properties:
+ increase:
+ type: array
+ items:
+ $ref: '#/components/schemas/NatureSummary'
+ decrease:
+ type: array
+ items:
+ $ref: '#/components/schemas/NatureSummary'
+ required:
+ - decrease
+ - increase
StatDetail:
type: object
properties:
@@ -9032,127 +7996,17 @@ components:
is_battle_only:
type: boolean
affecting_moves:
- type: object
- required:
- - decrease
- - increase
- properties:
- increase:
- type: array
- items:
- type: object
- required:
- - change
- - move
- properties:
- change:
- type: integer
- format: int32
- examples:
- - -1
- move:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - swords-dance
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/14/
- decrease:
- type: array
- items:
- type: object
- required:
- - change
- - move
- properties:
- change:
- type: integer
- format: int32
- examples:
- - 5
- move:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - growl
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move/45/
+ allOf:
+ - $ref: '#/components/schemas/StatAffectingMoves'
readOnly: true
affecting_natures:
- type: object
- required:
- - increase
- - decrease
- properties:
- increase:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - lonely
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/nature/6/
- decrease:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bold
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/nature/2/
+ allOf:
+ - $ref: '#/components/schemas/StatAffectingNatures'
readOnly: true
affecting_items:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - protein
- - x-attack
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/item/46/
+ $ref: '#/components/schemas/ItemSummary'
readOnly: true
characteristics:
type: array
@@ -9256,253 +8110,13 @@ components:
type: string
maxLength: 200
damage_relations:
- type: object
- required:
- - no_damage_to
- - half_damage_to
- - double_damage_to
- - no_damage_from
- - half_damage_from
- - double_damage_from
- properties:
- no_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - flying
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/3/
- half_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bug
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/7/
- double_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - poison
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/4/
- no_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - electric
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/13/
- half_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - poison
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/4/
- double_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - water
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/11/
+ allOf:
+ - $ref: '#/components/schemas/TypeRelationships'
readOnly: true
past_damage_relations:
type: array
items:
- type: object
- required:
- - generation
- - damage_relations
- properties:
- generation:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - generation-v
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/generation/5/
- damage_relations:
- type: object
- required:
- - no_damage_to
- - half_damage_to
- - double_damage_to
- - no_damage_from
- - half_damage_from
- - double_damage_from
- properties:
- no_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - flying
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/3/
- half_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - bug
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/7/
- double_damage_to:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - poison
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/4/
- no_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - electric
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/13/
- half_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - poison
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/4/
- double_damage_from:
- type: array
- items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - water
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/type/11/
+ $ref: '#/components/schemas/TypePastRelationships'
readOnly: true
game_indices:
type: array
@@ -9516,37 +8130,12 @@ components:
names:
type: array
items:
- $ref: '#/components/schemas/AbilityName'
+ $ref: '#/components/schemas/TypeName'
readOnly: true
pokemon:
type: array
items:
- type: object
- required:
- - potency
- - flavor
- properties:
- slot:
- type: integer
- examples:
- - 1
- pokemon:
- type: object
- require:
- - name
- - url
- properties:
- name:
- type: string
- description: The name of the pokemon
- examples:
- - sandshrew
- url:
- type: string
- format: uri
- description: The URL to get more information about the pokemon
- examples:
- - https://pokeapi.co/api/v2/pokemon/27/
+ $ref: '#/components/schemas/TypePokemon'
readOnly: true
moves:
type: array
@@ -9554,37 +8143,8 @@ components:
$ref: '#/components/schemas/MoveSummary'
readOnly: true
sprites:
- type: object
- additionalProperties:
- type: object
- additionalProperties:
- type: object
- properties:
- name-icon:
- type: string
- format: uri
- examples:
- - https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png
- examples:
- - colosseum:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png
- examples:
- - generation-ix:
- scarlet-violet:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-ix/scarlet-violet/1.png
- examples:
- - sprites:
- generation-iii:
- colosseum:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png
- emerald:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/emerald/1.png
- firered-leafgreen:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/firered-leafgreen/1.png
- ruby-sapphire:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/ruby-sapphire/1.png
- xd:
- name_icon: https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/xd/1.png
+ allOf:
+ - $ref: '#/components/schemas/TypeSprite'
readOnly: true
required:
- damage_relations
@@ -9610,6 +8170,79 @@ components:
required:
- game_index
- generation
+ TypeName:
+ type: object
+ properties:
+ name:
+ type: string
+ maxLength: 200
+ language:
+ $ref: '#/components/schemas/LanguageSummary'
+ required:
+ - language
+ - name
+ TypePastRelationships:
+ type: object
+ properties:
+ generation:
+ $ref: '#/components/schemas/GenerationSummary'
+ damage_relations:
+ $ref: '#/components/schemas/TypeRelationships'
+ required:
+ - damage_relations
+ - generation
+ TypePokemon:
+ type: object
+ properties:
+ slot:
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
+ pokemon:
+ $ref: '#/components/schemas/PokemonSummary'
+ required:
+ - pokemon
+ - slot
+ TypeRelationships:
+ type: object
+ properties:
+ no_damage_to:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ half_damage_to:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ double_damage_to:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ no_damage_from:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ half_damage_from:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ double_damage_from:
+ type: array
+ items:
+ $ref: '#/components/schemas/TypeSummary'
+ required:
+ - double_damage_from
+ - double_damage_to
+ - half_damage_from
+ - half_damage_to
+ - no_damage_from
+ - no_damage_to
+ TypeSprite:
+ type: object
+ properties:
+ sprites: {}
+ required:
+ - sprites
TypeSummary:
type: object
properties:
@@ -9668,56 +8301,17 @@ components:
move_learn_methods:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - level-up
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/move-learn-method/1/
+ $ref: '#/components/schemas/MoveLearnMethodSummary'
readOnly: true
pokedexes:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - kanto
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/pokedex/2/
+ $ref: '#/components/schemas/PokedexSummary'
readOnly: true
regions:
type: array
items:
- type: object
- required:
- - name
- - url
- properties:
- name:
- type: string
- examples:
- - kanto
- url:
- type: string
- format: uri
- examples:
- - https://pokeapi.co/api/v2/region/1/
+ $ref: '#/components/schemas/RegionSummary'
readOnly: true
versions:
type: array
diff --git a/pokemon_v2/api.py b/pokemon_v2/api.py
index e69d6ef8c..75039128c 100644
--- a/pokemon_v2/api.py
+++ b/pokemon_v2/api.py
@@ -1,71 +1,154 @@
+# ruff: noqa: F405, E501
+from __future__ import annotations
+
+import itertools
import re
import subprocess
-from rest_framework import viewsets
-from rest_framework.response import Response
-from rest_framework.views import APIView
-from django.shortcuts import get_object_or_404
+from typing import TYPE_CHECKING, Any, cast
+
+from django.core.exceptions import FieldError
+from django.db.models import Q, QuerySet
from django.http import Http404
-from django.db.models import Q
-from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
+from django.shortcuts import get_object_or_404
from drf_spectacular.types import OpenApiTypes
+from drf_spectacular.utils import (
+ OpenApiParameter,
+ extend_schema, # pyright: ignore[reportUnknownVariableType]
+ extend_schema_view, # pyright: ignore[reportUnknownVariableType]
+)
+from rest_framework import serializers, viewsets
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from typing_extensions import override
+
+from .models import * # noqa: F403
+from .serializers import * # noqa: F403
+
+if TYPE_CHECKING:
+ from rest_framework.request import Request
+ from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
+
+__all__: tuple[str, ...] = (
+ "AbilityResource",
+ "BerryFirmnessResource",
+ "BerryFlavorResource",
+ "BerryResource",
+ "CharacteristicResource",
+ "ContestEffectResource",
+ "ContestTypeResource",
+ "CurrencyResource",
+ "EggGroupResource",
+ "EncounterConditionResource",
+ "EncounterConditionValueResource",
+ "EncounterMethodResource",
+ "EvolutionChainResource",
+ "EvolutionTriggerResource",
+ "GenderResource",
+ "GenerationResource",
+ "GrowthRateResource",
+ "ItemAttributeResource",
+ "ItemCategoryResource",
+ "ItemFlingEffectResource",
+ "ItemPocketResource",
+ "ItemResource",
+ "LanguageResource",
+ "ListOrDetailSerialRelation",
+ "LocationAreaResource",
+ "LocationResource",
+ "MachineResource",
+ "MoveBattleStyleResource",
+ "MoveDamageClassResource",
+ "MoveLearnMethodResource",
+ "MoveMetaAilmentResource",
+ "MoveMetaCategoryResource",
+ "MoveResource",
+ "MoveTargetResource",
+ "NameOrIdRetrieval",
+ "NatureResource",
+ "PalParkAreaResource",
+ "PokeapiCommonViewset",
+ "PokeapiMetaResponseSerializer",
+ "PokeapiMetaView",
+ "PokeathlonStatResource",
+ "PokedexResource",
+ "PokemonColorResource",
+ "PokemonEncounterDetailResponseSerializer",
+ "PokemonEncounterResponseSerializer",
+ "PokemonEncounterVersionDetailResponseSerializer",
+ "PokemonEncounterView",
+ "PokemonFormResource",
+ "PokemonHabitatResource",
+ "PokemonResource",
+ "PokemonShapeResource",
+ "PokemonSpeciesResource",
+ "RegionResource",
+ "StatResource",
+ "SuperContestEffectResource",
+ "TypeResource",
+ "VersionGroupResource",
+ "VersionResource",
+)
-from .models import *
-from .serializers import *
-
-# pylint: disable=no-member, attribute-defined-outside-init
###########################
# BEHAVIOR ABSTRACTIONS #
###########################
-class ListOrDetailSerialRelation:
+class ListOrDetailSerialRelation(viewsets.GenericViewSet[Any]):
"""
- Mixin to allow association with separate serializers
- for list or detail view.
+ Mixin to allow association with separate serializers for list or detail view.
"""
- list_serializer_class = None
+ list_serializer_class: type[serializers.BaseSerializer[Any]] | None = None
- def get_serializer_class(self):
- if self.action == "list" and self.list_serializer_class is not None:
- return self.list_serializer_class
+ @override
+ def get_serializer_class(self) -> type[serializers.BaseSerializer[Any]]:
+ if self.action == "list":
+ if self.list_serializer_class is not None:
+ return self.list_serializer_class
+ raise AttributeError("list_serializer_class must be set for list view")
+ if self.serializer_class is None:
+ raise AttributeError("serializer_class must be set for detail view")
return self.serializer_class
-class NameOrIdRetrieval:
+class NameOrIdRetrieval(viewsets.GenericViewSet[Any]):
"""
- Mixin to allow retrieval of resources by
- pk (in this case ID) or by name
+ Mixin to allow retrieval of resources by pk (in this case ID) or by name.
"""
- idPattern = re.compile(r"^-?[0-9]+$")
+ ID_PATTERN = re.compile(r"^-?[0-9]+$")
# Allow alphanumeric, hyphen, plus, and space (Space added for test cases using name for lookup, ex: 'base pkm')
- namePattern = re.compile(r"^[0-9A-Za-z\-\+ ]+$")
+ NAME_PATTERN = re.compile(r"^[0-9A-Za-z\-\+ ]+$")
- def get_queryset(self):
+ @override
+ def get_queryset(self) -> QuerySet[Any]:
queryset = super().get_queryset()
- filter = self.request.GET.get("q", "")
+ filter_q = self.request.GET.get("q", "")
- if filter:
- queryset = queryset.filter(Q(name__icontains=filter))
+ if filter_q:
+ queryset = queryset.filter(Q(name__icontains=filter_q))
return queryset
- def get_object(self):
- queryset = self.get_queryset()
- queryset = self.filter_queryset(queryset)
+ @override
+ def get_object(self) -> Any:
+ queryset = self.filter_queryset(self.get_queryset())
lookup = self.kwargs["pk"]
- if self.idPattern.match(lookup):
+ if self.ID_PATTERN.match(lookup):
lookup_id = int(lookup)
if abs(lookup_id) > 2147483647:
raise Http404
- resp = get_object_or_404(queryset, pk=lookup)
+ resp = get_object_or_404(queryset, pk=lookup_id)
- elif self.namePattern.match(lookup):
- resp = get_object_or_404(queryset, name__iexact=lookup)
+ elif self.NAME_PATTERN.match(lookup):
+ try:
+ resp = get_object_or_404(queryset, name__iexact=lookup)
+ except FieldError as err:
+ raise Http404 from err
else:
raise Http404
@@ -90,12 +173,11 @@ def get_object(self):
@extend_schema_view(list=extend_schema(parameters=[q_query_string_parameter]))
-class PokeapiCommonViewset(ListOrDetailSerialRelation, NameOrIdRetrieval, viewsets.ReadOnlyModelViewSet):
+class PokeapiCommonViewset(ListOrDetailSerialRelation, NameOrIdRetrieval, viewsets.ReadOnlyModelViewSet[Any]):
+ @override
@extend_schema(parameters=[retrieve_path_parameter])
- def retrieve(self, request, pk=None):
- return super().retrieve(request, pk)
-
- pass
+ def retrieve(self, request: Request, *args: Any, pk: str | int | None = None, **kwargs: Any) -> Response:
+ return super().retrieve(request, *args, pk=pk, **kwargs)
##########
@@ -114,7 +196,7 @@ def retrieve(self, request, pk=None):
)
)
class AbilityResource(PokeapiCommonViewset):
- queryset = Ability.objects.all()
+ queryset = Ability.objects.select_related("generation")
serializer_class = AbilityDetailSerializer
list_serializer_class = AbilitySummarySerializer
@@ -497,7 +579,7 @@ class LocationResource(PokeapiCommonViewset):
summary="List location areas",
)
)
-class LocationAreaResource(ListOrDetailSerialRelation, viewsets.ReadOnlyModelViewSet):
+class LocationAreaResource(ListOrDetailSerialRelation, viewsets.ReadOnlyModelViewSet[LocationArea]):
queryset = LocationArea.objects.all()
serializer_class = LocationAreaDetailSerializer
list_serializer_class = LocationAreaSummarySerializer
@@ -770,7 +852,7 @@ class PokemonShapeResource(PokeapiCommonViewset):
),
)
class PokemonResource(PokeapiCommonViewset):
- queryset = Pokemon.objects.all()
+ queryset = Pokemon.objects.select_related("pokemon_species").prefetch_related("pokemonsprites", "pokemoncries")
serializer_class = PokemonDetailSerializer
list_serializer_class = PokemonSummarySerializer
@@ -887,204 +969,105 @@ class VersionGroupResource(PokeapiCommonViewset):
list_serializer_class = VersionGroupSummarySerializer
-@extend_schema(
- description="Handles Pokemon Encounters as a sub-resource.",
- summary="Get pokemon encounter",
- tags=["encounters"],
- responses={
- "200": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["location_area", "version_details"],
- "properties": {
- "location_area": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "example": "cerulean-city-area"},
- "url": {
- "type": "string",
- "format": "uri",
- "example": "https://pokeapi.co/api/v2/location-area/281/",
- },
- },
- },
- "version_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["encounter_details", "max_chance", "version"],
- "properties": {
- "encounter_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [
- "chance",
- "condition_values",
- "max_level",
- "method",
- "min_level",
- ],
- "properties": {
- "chance": {
- "type": "number",
- "example": 100,
- },
- "condition_values": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "example": "story-progress-beat-red",
- },
- "url": {
- "type": "string",
- "format": "uri",
- "example": "https://pokeapi.co/api/v2/encounter-condition-value/55/",
- },
- },
- },
- },
- "max_level": {
- "type": "number",
- "example": 10,
- },
- "method": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "example": "gift",
- },
- "url": {
- "type": "string",
- "format": "uri",
- "example": "https://pokeapi.co/api/v2/encounter-method/18/",
- },
- },
- },
- "min_level": {
- "type": "number",
- "example": 10,
- },
- },
- },
- },
- "max_chance": {"type": "number", "example": 100},
- "version": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "example": "red"},
- "url": {
- "type": "string",
- "format": "uri",
- "example": "https://pokeapi.co/api/v2/version/1/",
- },
- },
- },
- },
- },
- },
- },
- },
- }
- },
-)
-class PokemonEncounterView(APIView):
- def get(self, request, pokemon_id):
- self.context = dict(request=request)
-
- try:
- pokemon = Pokemon.objects.get(pk=pokemon_id)
- except Pokemon.DoesNotExist:
- raise Http404
-
- encounter_objects = Encounter.objects.filter(pokemon=pokemon)
-
- area_ids = encounter_objects.values_list("location_area", flat=True).distinct().order_by("location_area")
-
- location_area_objects = LocationArea.objects.filter(pk__in=area_ids)
- version_objects = Version.objects
-
- encounters_list = []
-
- for area_id in area_ids:
- location_area = location_area_objects.get(pk=area_id)
+class PokemonEncounterDetailResponseSerializer(serializers.Serializer[dict[str, Any]]):
+ chance = serializers.IntegerField()
+ condition_values = EncounterConditionValueSummarySerializer(many=True)
+ max_level = serializers.IntegerField()
+ method = EncounterMethodSummarySerializer()
+ min_level = serializers.IntegerField()
- area_encounters = encounter_objects.filter(location_area_id=area_id)
- version_ids = area_encounters.values_list("version_id", flat=True).distinct().order_by("version_id")
- version_details_list = []
+class PokemonEncounterVersionDetailResponseSerializer(serializers.Serializer[dict[str, Any]]):
+ version = VersionSummarySerializer()
+ max_chance = serializers.IntegerField()
+ encounter_details = PokemonEncounterDetailResponseSerializer(many=True)
- for version_id in version_ids:
- version = version_objects.get(pk=version_id)
- version_encounters = area_encounters.filter(version_id=version_id).order_by("encounter_slot_id")
+class PokemonEncounterResponseSerializer(serializers.Serializer[dict[str, Any]]):
+ location_area = LocationAreaSummarySerializer()
+ version_details = PokemonEncounterVersionDetailResponseSerializer(many=True)
- encounters_data = EncounterDetailSerializer(version_encounters, many=True, context=self.context).data
- max_chance = 0
- encounter_details_list = []
+class PokemonLocationAreaEncounterSerializer(serializers.Serializer[Any]):
+ location_area = LocationAreaSummarySerializer()
+ version_details = LocationAreaPokemonEncounterVersionSerializer(many=True)
- for encounter in encounters_data:
- slot = EncounterSlot.objects.get(pk=encounter["encounter_slot"])
- slot_data = EncounterSlotSerializer(slot, context=self.context).data
- del encounter["pokemon"]
- del encounter["encounter_slot"]
- del encounter["location_area"]
- del encounter["version"]
- encounter["chance"] = slot_data["chance"]
- max_chance += slot_data["chance"]
- encounter["method"] = slot_data["encounter_method"]
+@extend_schema(
+ description="Handles Pokemon Encounters as a sub-resource.",
+ summary="Get pokemon encounter",
+ tags=["encounters"],
+ responses={"200": PokemonEncounterResponseSerializer(many=True)},
+)
+class PokemonEncounterView(APIView):
+ def get(self, request: Request, pokemon_id: int) -> Response:
+ self.context = {"request": request}
- encounter_details_list.append(encounter)
+ try:
+ pokemon = Pokemon.objects.get(pk=pokemon_id)
+ except Pokemon.DoesNotExist as e:
+ raise Http404 from e
+
+ encounters = (
+ Encounter.objects.filter(pokemon=pokemon)
+ .select_related(
+ "location_area",
+ "version",
+ "encounter_slot",
+ "encounter_slot__encounter_method",
+ )
+ .prefetch_related(
+ "encounterconditionvaluemap_set",
+ "encounterconditionvaluemap_set__encounter_condition_value",
+ "encounterpokemondetail_set",
+ )
+ .order_by("location_area_id", "version_id", "encounter_slot_id")
+ )
- version_details_list.append(
+ grouped_data: list[dict[str, Any]] = []
+ for location_area, area_group in itertools.groupby(encounters, key=lambda e: e.location_area):
+ version_details: list[dict[str, Any]] = []
+ for version, ver_group in itertools.groupby(area_group, key=lambda e: e.version):
+ encounter_list = list(ver_group)
+ max_chance = sum(e.encounter_slot.rarity for e in encounter_list if e.encounter_slot)
+ version_details.append(
{
- "version": VersionSummarySerializer(version, context=self.context).data,
+ "version": version,
"max_chance": max_chance,
- "encounter_details": encounter_details_list,
+ "encounter_details": encounter_list,
}
)
-
- encounters_list.append(
+ grouped_data.append(
{
- "location_area": LocationAreaSummarySerializer(location_area, context=self.context).data,
- "version_details": version_details_list,
+ "location_area": location_area,
+ "version_details": version_details,
}
)
- return Response(encounters_list)
+ data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonLocationAreaEncounterSerializer(grouped_data, many=True, context=self.context).data, # pyright: ignore[reportUnknownMemberType]
+ )
+ return Response(data)
+
+
+class PokeapiMetaResponseSerializer(serializers.Serializer[dict[str, Any]]):
+ deploy_date = serializers.CharField(allow_null=True)
+ hash = serializers.CharField(allow_null=True)
+ tag = serializers.CharField(allow_null=True)
@extend_schema(
description="Returns metadata about the current deployed version of the API, including the git commit hash, deploy date, and tag (if any).",
summary="Get API metadata",
tags=["utility"],
- responses={
- 200: {
- "type": "object",
- "properties": {
- "deploy_date": {"type": "string", "nullable": True},
- "hash": {"type": "string", "nullable": True},
- "tag": {"type": "string", "nullable": True},
- },
- }
- },
+ responses={"200": PokeapiMetaResponseSerializer},
)
class PokeapiMetaView(APIView):
- def get(self, request):
+ def get(self, _request: Request) -> Response:
try:
git_hash = subprocess.check_output(["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL).decode().strip()
- except Exception:
+ except (subprocess.CalledProcessError, OSError, ValueError):
git_hash = None
try:
@@ -1093,7 +1076,7 @@ def get(self, request):
.decode()
.strip()
)
- except Exception:
+ except (subprocess.CalledProcessError, OSError, ValueError):
deploy_date = None
try:
@@ -1102,8 +1085,8 @@ def get(self, request):
.decode()
.strip()
)
- tag = tag_output if tag_output else None
- except Exception:
+ tag = tag_output or None
+ except (subprocess.CalledProcessError, OSError, ValueError):
tag = None
return Response(
diff --git a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py
index e0f591830..e037a683f 100644
--- a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py
+++ b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0003_auto_20160530_1132.py b/pokemon_v2/migrations/0003_auto_20160530_1132.py
index a5f27cf9d..b70b66aa8 100644
--- a/pokemon_v2/migrations/0003_auto_20160530_1132.py
+++ b/pokemon_v2/migrations/0003_auto_20160530_1132.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0004_iso639length_20191217.py b/pokemon_v2/migrations/0004_iso639length_20191217.py
index bff709f2c..ff9cdaf78 100644
--- a/pokemon_v2/migrations/0004_iso639length_20191217.py
+++ b/pokemon_v2/migrations/0004_iso639length_20191217.py
@@ -1,4 +1,4 @@
-from django.db import models, migrations
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0009_pokemontypepast.py b/pokemon_v2/migrations/0009_pokemontypepast.py
index a9351a893..0a8a22ca7 100644
--- a/pokemon_v2/migrations/0009_pokemontypepast.py
+++ b/pokemon_v2/migrations/0009_pokemontypepast.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.11 on 2021-02-06 22:03
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0010_pokemonformtype.py b/pokemon_v2/migrations/0010_pokemonformtype.py
index 91046acbf..3b577d58c 100644
--- a/pokemon_v2/migrations/0010_pokemonformtype.py
+++ b/pokemon_v2/migrations/0010_pokemonformtype.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.11 on 2021-02-18 20:45
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0011_typeefficacypast.py b/pokemon_v2/migrations/0011_typeefficacypast.py
index 7f96f04e3..3ea6362f1 100644
--- a/pokemon_v2/migrations/0011_typeefficacypast.py
+++ b/pokemon_v2/migrations/0011_typeefficacypast.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.11 on 2021-02-24 13:42
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0013_pokemonabilitypast.py b/pokemon_v2/migrations/0013_pokemonabilitypast.py
index 421b1f97c..eb74b2e2a 100644
--- a/pokemon_v2/migrations/0013_pokemonabilitypast.py
+++ b/pokemon_v2/migrations/0013_pokemonabilitypast.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.15 on 2023-02-27 15:33
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0015_pokemoncries.py b/pokemon_v2/migrations/0015_pokemoncries.py
index 05099ce1c..edae3ce23 100644
--- a/pokemon_v2/migrations/0015_pokemoncries.py
+++ b/pokemon_v2/migrations/0015_pokemoncries.py
@@ -1,7 +1,7 @@
# Generated by Django 3.2.23 on 2024-02-02 18:02
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0016_typesprites.py b/pokemon_v2/migrations/0016_typesprites.py
index a9e752d63..195e52139 100644
--- a/pokemon_v2/migrations/0016_typesprites.py
+++ b/pokemon_v2/migrations/0016_typesprites.py
@@ -1,7 +1,7 @@
# Generated by Django 3.2.23 on 2024-07-29 02:09
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0020_add_regional_evolution_fields.py b/pokemon_v2/migrations/0020_add_regional_evolution_fields.py
index 3834f8865..8f4f94737 100644
--- a/pokemon_v2/migrations/0020_add_regional_evolution_fields.py
+++ b/pokemon_v2/migrations/0020_add_regional_evolution_fields.py
@@ -1,7 +1,7 @@
# Generated migration for regional evolution metadata
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0021_add_evolution_methods_and_fields.py b/pokemon_v2/migrations/0021_add_evolution_methods_and_fields.py
index 65f59d430..bc19607be 100644
--- a/pokemon_v2/migrations/0021_add_evolution_methods_and_fields.py
+++ b/pokemon_v2/migrations/0021_add_evolution_methods_and_fields.py
@@ -1,7 +1,7 @@
# Generated by Django 3.2.25 on 2025-12-31 17:47
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/pokemon_v2/migrations/0032_pokemonformcondition_base_form.py b/pokemon_v2/migrations/0032_evolution_condition_and_form_updates.py
similarity index 75%
rename from pokemon_v2/migrations/0032_pokemonformcondition_base_form.py
rename to pokemon_v2/migrations/0032_evolution_condition_and_form_updates.py
index c5b5db217..3ba5ce360 100644
--- a/pokemon_v2/migrations/0032_pokemonformcondition_base_form.py
+++ b/pokemon_v2/migrations/0032_evolution_condition_and_form_updates.py
@@ -8,6 +8,11 @@ class Migration(migrations.Migration):
]
operations = [
+ migrations.AlterField(
+ model_name="pokemonevolution",
+ name="time_of_day",
+ field=models.CharField(blank=True, default="", max_length=10),
+ ),
migrations.AddField(
model_name="pokemonformcondition",
name="base_form",
diff --git a/pokemon_v2/migrations/0033_itemprice.py b/pokemon_v2/migrations/0033_item_prices_and_currencies.py
similarity index 95%
rename from pokemon_v2/migrations/0033_itemprice.py
rename to pokemon_v2/migrations/0033_item_prices_and_currencies.py
index daf8a6942..a07256c1c 100644
--- a/pokemon_v2/migrations/0033_itemprice.py
+++ b/pokemon_v2/migrations/0033_item_prices_and_currencies.py
@@ -1,12 +1,10 @@
-# Generated by Django 5.2.10 on 2026-03-07 00:00
-
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ("pokemon_v2", "0032_pokemonformcondition_base_form"),
+ ("pokemon_v2", "0032_evolution_condition_and_form_updates"),
]
operations = [
@@ -115,4 +113,8 @@ class Migration(migrations.Migration):
"abstract": False,
},
),
+ migrations.RemoveField(
+ model_name="item",
+ name="cost",
+ ),
]
diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py
index d77bd5138..7998e1a7c 100644
--- a/pokemon_v2/models.py
+++ b/pokemon_v2/models.py
@@ -1,11 +1,250 @@
+# pyright: reportIncompatibleVariableOverride=false
+from __future__ import annotations
+
+from typing import Any
+
from django.db import models
+from typing_extensions import override
+
+__all__: tuple[str, ...] = (
+ "Ability",
+ "AbilityChange",
+ "AbilityChangeEffectText",
+ "AbilityEffectText",
+ "AbilityFlavorText",
+ "AbilityName",
+ "Berry",
+ "BerryFirmness",
+ "BerryFirmnessName",
+ "BerryFlavor",
+ "BerryFlavorMap",
+ "BerryFlavorName",
+ "Characteristic",
+ "CharacteristicDescription",
+ "ContestCombo",
+ "ContestEffect",
+ "ContestEffectEffectText",
+ "ContestEffectFlavorText",
+ "ContestType",
+ "ContestTypeName",
+ "Currency",
+ "CurrencyName",
+ "EggGroup",
+ "EggGroupName",
+ "Encounter",
+ "EncounterCondition",
+ "EncounterConditionName",
+ "EncounterConditionValue",
+ "EncounterConditionValueMap",
+ "EncounterConditionValueName",
+ "EncounterMethod",
+ "EncounterMethodName",
+ "EncounterPokemonDetail",
+ "EncounterSlot",
+ "EvolutionChain",
+ "EvolutionTrigger",
+ "EvolutionTriggerName",
+ "Experience",
+ "Gender",
+ "Generation",
+ "GenerationName",
+ "GrowthRate",
+ "GrowthRateDescription",
+ "HasAbility",
+ "HasCharacteristic",
+ "HasContestEffect",
+ "HasContestType",
+ "HasDescription",
+ "HasEffect",
+ "HasEggGroup",
+ "HasEncounterCondition",
+ "HasEncounterMethod",
+ "HasEvolutionTrigger",
+ "HasFlavorText",
+ "HasFlingEffect",
+ "HasGameIndex",
+ "HasGender",
+ "HasGeneration",
+ "HasGrowthRate",
+ "HasItem",
+ "HasItemAttribute",
+ "HasItemCategory",
+ "HasItemPocket",
+ "HasLanguage",
+ "HasLocation",
+ "HasLocationArea",
+ "HasMetaAilment",
+ "HasMetaCategory",
+ "HasMove",
+ "HasMoveAttribute",
+ "HasMoveDamageClass",
+ "HasMoveEffect",
+ "HasMoveLearnMethod",
+ "HasMoveTarget",
+ "HasName",
+ "HasNature",
+ "HasOrder",
+ "HasPokeathlonStat",
+ "HasPokedex",
+ "HasPokemon",
+ "HasPokemonColor",
+ "HasPokemonForm",
+ "HasPokemonHabitat",
+ "HasPokemonShape",
+ "HasPokemonSpecies",
+ "HasRegion",
+ "HasShortEffect",
+ "HasStat",
+ "HasSuperContestEffect",
+ "HasType",
+ "HasTypeEfficacy",
+ "HasVersion",
+ "HasVersionGroup",
+ "IsDescription",
+ "IsFlavorText",
+ "IsName",
+ "Item",
+ "ItemAttribute",
+ "ItemAttributeDescription",
+ "ItemAttributeMap",
+ "ItemAttributeName",
+ "ItemCategory",
+ "ItemCategoryName",
+ "ItemEffectText",
+ "ItemFlavorText",
+ "ItemFlingEffect",
+ "ItemFlingEffectEffectText",
+ "ItemGameIndex",
+ "ItemName",
+ "ItemPocket",
+ "ItemPocketName",
+ "ItemPrice",
+ "ItemSprites",
+ "Language",
+ "LanguageName",
+ "Location",
+ "LocationArea",
+ "LocationAreaEncounterRate",
+ "LocationAreaName",
+ "LocationGameIndex",
+ "LocationName",
+ "Machine",
+ "Move",
+ "MoveAttribute",
+ "MoveAttributeDescription",
+ "MoveAttributeMap",
+ "MoveAttributeName",
+ "MoveBattleStyle",
+ "MoveBattleStyleName",
+ "MoveChange",
+ "MoveDamageClass",
+ "MoveDamageClassDescription",
+ "MoveDamageClassName",
+ "MoveEffect",
+ "MoveEffectChange",
+ "MoveEffectChangeEffectText",
+ "MoveEffectEffectText",
+ "MoveFlavorText",
+ "MoveLearnMethod",
+ "MoveLearnMethodDescription",
+ "MoveLearnMethodName",
+ "MoveMeta",
+ "MoveMetaAilment",
+ "MoveMetaAilmentName",
+ "MoveMetaCategory",
+ "MoveMetaCategoryDescription",
+ "MoveMetaStatChange",
+ "MoveName",
+ "MoveTarget",
+ "MoveTargetDescription",
+ "MoveTargetName",
+ "Nature",
+ "NatureBattleStylePreference",
+ "NatureName",
+ "NaturePokeathlonStat",
+ "PalPark",
+ "PalParkArea",
+ "PalParkAreaName",
+ "PokeApiModel",
+ "PokeathlonStat",
+ "PokeathlonStatName",
+ "Pokedex",
+ "PokedexDescription",
+ "PokedexName",
+ "PokedexVersionGroup",
+ "Pokemon",
+ "PokemonAbility",
+ "PokemonAbilityPast",
+ "PokemonColor",
+ "PokemonColorName",
+ "PokemonCries",
+ "PokemonDexNumber",
+ "PokemonEggGroup",
+ "PokemonEvolution",
+ "PokemonForm",
+ "PokemonFormCondition",
+ "PokemonFormGeneration",
+ "PokemonFormName",
+ "PokemonFormSprites",
+ "PokemonFormTrigger",
+ "PokemonFormType",
+ "PokemonGameIndex",
+ "PokemonHabitat",
+ "PokemonHabitatName",
+ "PokemonItem",
+ "PokemonMove",
+ "PokemonShape",
+ "PokemonShapeName",
+ "PokemonSpecies",
+ "PokemonSpeciesDescription",
+ "PokemonSpeciesFlavorText",
+ "PokemonSpeciesName",
+ "PokemonSprites",
+ "PokemonStat",
+ "PokemonStatPast",
+ "PokemonType",
+ "PokemonTypePast",
+ "Region",
+ "RegionName",
+ "Stat",
+ "StatName",
+ "SuperContestCombo",
+ "SuperContestEffect",
+ "SuperContestEffectFlavorText",
+ "Type",
+ "TypeEfficacy",
+ "TypeEfficacyPast",
+ "TypeGameIndex",
+ "TypeName",
+ "TypeSprites",
+ "Version",
+ "VersionGroup",
+ "VersionGroupMoveLearnMethod",
+ "VersionGroupRegion",
+ "VersionName",
+)
+
+
+############################
+# BASE MODEL FOR POKEAPI #
+############################
+
+
+class PokeApiModel(models.Model):
+ class Meta:
+ abstract = True
+
+ @override
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}({self.pk})"
+
#####################
# ABSTRACT MODELS #
#####################
-class HasAbility(models.Model):
+class HasAbility(PokeApiModel):
ability = models.ForeignKey(
"Ability",
blank=True,
@@ -18,7 +257,7 @@ class Meta:
abstract = True
-class HasCharacteristic(models.Model):
+class HasCharacteristic(PokeApiModel):
characteristic = models.ForeignKey(
"Characteristic",
blank=True,
@@ -31,7 +270,7 @@ class Meta:
abstract = True
-class HasContestType(models.Model):
+class HasContestType(PokeApiModel):
contest_type = models.ForeignKey(
"ContestType",
blank=True,
@@ -44,7 +283,7 @@ class Meta:
abstract = True
-class HasContestEffect(models.Model):
+class HasContestEffect(PokeApiModel):
contest_effect = models.ForeignKey(
"ContestEffect",
blank=True,
@@ -57,7 +296,7 @@ class Meta:
abstract = True
-class HasCurrency(models.Model):
+class HasCurrency(PokeApiModel):
currency = models.ForeignKey(
"Currency",
blank=True,
@@ -70,7 +309,7 @@ class Meta:
abstract = True
-class HasSuperContestEffect(models.Model):
+class HasSuperContestEffect(PokeApiModel):
super_contest_effect = models.ForeignKey(
"SuperContestEffect",
blank=True,
@@ -83,14 +322,14 @@ class Meta:
abstract = True
-class HasDescription(models.Model):
+class HasDescription(PokeApiModel):
description = models.CharField(max_length=2000, default="")
class Meta:
abstract = True
-class HasGender(models.Model):
+class HasGender(PokeApiModel):
gender = models.ForeignKey(
"Gender",
blank=True,
@@ -103,14 +342,14 @@ class Meta:
abstract = True
-class HasEffect(models.Model):
+class HasEffect(PokeApiModel):
effect = models.CharField(max_length=6000)
class Meta:
abstract = True
-class HasEggGroup(models.Model):
+class HasEggGroup(PokeApiModel):
egg_group = models.ForeignKey(
"EggGroup",
blank=True,
@@ -123,7 +362,7 @@ class Meta:
abstract = True
-class HasEncounterMethod(models.Model):
+class HasEncounterMethod(PokeApiModel):
encounter_method = models.ForeignKey(
"EncounterMethod",
blank=True,
@@ -136,7 +375,7 @@ class Meta:
abstract = True
-class HasEncounterCondition(models.Model):
+class HasEncounterCondition(PokeApiModel):
encounter_condition = models.ForeignKey(
"EncounterCondition",
blank=True,
@@ -149,7 +388,7 @@ class Meta:
abstract = True
-class HasEvolutionTrigger(models.Model):
+class HasEvolutionTrigger(PokeApiModel):
evolution_trigger = models.ForeignKey(
"EvolutionTrigger",
blank=True,
@@ -162,14 +401,14 @@ class Meta:
abstract = True
-class HasFlavorText(models.Model):
+class HasFlavorText(PokeApiModel):
flavor_text = models.CharField(max_length=500)
class Meta:
abstract = True
-class HasFlingEffect(models.Model):
+class HasFlingEffect(PokeApiModel):
item_fling_effect = models.ForeignKey(
"ItemFlingEffect",
blank=True,
@@ -182,14 +421,14 @@ class Meta:
abstract = True
-class HasGameIndex(models.Model):
+class HasGameIndex(PokeApiModel):
game_index = models.IntegerField()
class Meta:
abstract = True
-class HasGeneration(models.Model):
+class HasGeneration(PokeApiModel):
generation = models.ForeignKey(
"Generation",
blank=True,
@@ -202,7 +441,7 @@ class Meta:
abstract = True
-class HasGrowthRate(models.Model):
+class HasGrowthRate(PokeApiModel):
growth_rate = models.ForeignKey(
"GrowthRate",
blank=True,
@@ -215,7 +454,7 @@ class Meta:
abstract = True
-class HasItem(models.Model):
+class HasItem(PokeApiModel):
item = models.ForeignKey(
"Item",
blank=True,
@@ -228,7 +467,7 @@ class Meta:
abstract = True
-class HasItemAttribute(models.Model):
+class HasItemAttribute(PokeApiModel):
item_attribute = models.ForeignKey(
"ItemAttribute",
blank=True,
@@ -241,7 +480,7 @@ class Meta:
abstract = True
-class HasItemCategory(models.Model):
+class HasItemCategory(PokeApiModel):
item_category = models.ForeignKey(
"ItemCategory",
blank=True,
@@ -254,7 +493,7 @@ class Meta:
abstract = True
-class HasItemPocket(models.Model):
+class HasItemPocket(PokeApiModel):
item_pocket = models.ForeignKey(
"ItemPocket",
blank=True,
@@ -267,7 +506,7 @@ class Meta:
abstract = True
-class HasLanguage(models.Model):
+class HasLanguage(PokeApiModel):
language = models.ForeignKey(
"Language",
blank=True,
@@ -280,7 +519,7 @@ class Meta:
abstract = True
-class HasLocation(models.Model):
+class HasLocation(PokeApiModel):
location = models.ForeignKey(
"Location",
blank=True,
@@ -293,7 +532,7 @@ class Meta:
abstract = True
-class HasLocationArea(models.Model):
+class HasLocationArea(PokeApiModel):
location_area = models.ForeignKey(
"LocationArea",
blank=True,
@@ -306,7 +545,7 @@ class Meta:
abstract = True
-class HasMetaAilment(models.Model):
+class HasMetaAilment(PokeApiModel):
move_meta_ailment = models.ForeignKey(
"MoveMetaAilment",
blank=True,
@@ -319,7 +558,7 @@ class Meta:
abstract = True
-class HasMetaCategory(models.Model):
+class HasMetaCategory(PokeApiModel):
move_meta_category = models.ForeignKey(
"MoveMetaCategory",
blank=True,
@@ -332,7 +571,7 @@ class Meta:
abstract = True
-class HasMove(models.Model):
+class HasMove(PokeApiModel):
move = models.ForeignKey(
"Move",
blank=True,
@@ -345,7 +584,7 @@ class Meta:
abstract = True
-class HasMoveDamageClass(models.Model):
+class HasMoveDamageClass(PokeApiModel):
move_damage_class = models.ForeignKey(
"MoveDamageClass",
blank=True,
@@ -358,21 +597,21 @@ class Meta:
abstract = True
-class HasMoveEffect(models.Model):
+class HasMoveEffect(PokeApiModel):
move_effect = models.ForeignKey("MoveEffect", blank=True, null=True, on_delete=models.CASCADE)
class Meta:
abstract = True
-class HasMoveAttribute(models.Model):
+class HasMoveAttribute(PokeApiModel):
move_attribute = models.ForeignKey("MoveAttribute", blank=True, null=True, on_delete=models.CASCADE)
class Meta:
abstract = True
-class HasMoveTarget(models.Model):
+class HasMoveTarget(PokeApiModel):
move_target = models.ForeignKey(
"MoveTarget",
blank=True,
@@ -385,14 +624,14 @@ class Meta:
abstract = True
-class HasName(models.Model):
+class HasName(PokeApiModel):
name = models.CharField(max_length=200, db_index=True)
class Meta:
abstract = True
-class HasNature(models.Model):
+class HasNature(PokeApiModel):
nature = models.ForeignKey(
"Nature",
blank=True,
@@ -405,14 +644,14 @@ class Meta:
abstract = True
-class HasOrder(models.Model):
+class HasOrder(PokeApiModel):
order = models.IntegerField(blank=True, null=True)
class Meta:
abstract = True
-class HasPokeathlonStat(models.Model):
+class HasPokeathlonStat(PokeApiModel):
pokeathlon_stat = models.ForeignKey(
"PokeathlonStat",
blank=True,
@@ -425,7 +664,7 @@ class Meta:
abstract = True
-class HasPokedex(models.Model):
+class HasPokedex(PokeApiModel):
pokedex = models.ForeignKey(
"Pokedex",
blank=True,
@@ -438,7 +677,7 @@ class Meta:
abstract = True
-class HasPokemon(models.Model):
+class HasPokemon(PokeApiModel):
pokemon = models.ForeignKey(
"Pokemon",
blank=True,
@@ -451,7 +690,7 @@ class Meta:
abstract = True
-class HasPokemonColor(models.Model):
+class HasPokemonColor(PokeApiModel):
pokemon_color = models.ForeignKey(
"PokemonColor",
blank=True,
@@ -464,7 +703,7 @@ class Meta:
abstract = True
-class HasPokemonForm(models.Model):
+class HasPokemonForm(PokeApiModel):
pokemon_form = models.ForeignKey(
"PokemonForm",
blank=True,
@@ -477,7 +716,7 @@ class Meta:
abstract = True
-class HasPokemonHabitat(models.Model):
+class HasPokemonHabitat(PokeApiModel):
pokemon_habitat = models.ForeignKey(
"PokemonHabitat",
blank=True,
@@ -491,7 +730,7 @@ class Meta:
# HasPokemonMoveMethod
-class HasMoveLearnMethod(models.Model):
+class HasMoveLearnMethod(PokeApiModel):
move_learn_method = models.ForeignKey(
"MoveLearnMethod",
blank=True,
@@ -504,7 +743,7 @@ class Meta:
abstract = True
-class HasPokemonShape(models.Model):
+class HasPokemonShape(PokeApiModel):
pokemon_shape = models.ForeignKey(
"PokemonShape",
blank=True,
@@ -517,7 +756,7 @@ class Meta:
abstract = True
-class HasPokemonSpecies(models.Model):
+class HasPokemonSpecies(PokeApiModel):
pokemon_species = models.ForeignKey(
"PokemonSpecies",
blank=True,
@@ -530,7 +769,7 @@ class Meta:
abstract = True
-class HasRegion(models.Model):
+class HasRegion(PokeApiModel):
region = models.ForeignKey(
"Region",
blank=True,
@@ -543,14 +782,14 @@ class Meta:
abstract = True
-class HasShortEffect(models.Model):
+class HasShortEffect(PokeApiModel):
short_effect = models.CharField(max_length=300)
class Meta:
abstract = True
-class HasStat(models.Model):
+class HasStat(PokeApiModel):
stat = models.ForeignKey(
"Stat",
blank=True,
@@ -563,7 +802,7 @@ class Meta:
abstract = True
-class HasType(models.Model):
+class HasType(PokeApiModel):
type = models.ForeignKey(
"Type",
blank=True,
@@ -576,7 +815,7 @@ class Meta:
abstract = True
-class HasTypeEfficacy(models.Model):
+class HasTypeEfficacy(PokeApiModel):
damage_type = models.ForeignKey(
"Type",
blank=True,
@@ -599,7 +838,7 @@ class Meta:
abstract = True
-class HasVersion(models.Model):
+class HasVersion(PokeApiModel):
version = models.ForeignKey(
"Version",
blank=True,
@@ -612,7 +851,7 @@ class Meta:
abstract = True
-class HasVersionGroup(models.Model):
+class HasVersionGroup(PokeApiModel):
version_group = models.ForeignKey(
"VersionGroup",
blank=True,
@@ -783,7 +1022,7 @@ class TypeEfficacyPast(HasTypeEfficacy, HasGeneration):
class TypeSprites(HasType):
- sprites = models.JSONField()
+ sprites: models.JSONField[Any] = models.JSONField()
#################
@@ -906,7 +1145,7 @@ class ItemPrice(HasItem, HasVersionGroup, HasCurrency):
class ItemSprites(HasItem):
- sprites = models.JSONField()
+ sprites: models.JSONField[Any] = models.JSONField()
####################
@@ -924,7 +1163,7 @@ class ContestTypeName(HasContestType, IsName):
color = models.CharField(max_length=10)
-class ContestEffect(models.Model):
+class ContestEffect(PokeApiModel):
appeal = models.IntegerField()
jam = models.IntegerField()
@@ -938,7 +1177,7 @@ class ContestEffectFlavorText(HasLanguage, HasFlavorText, HasContestEffect):
pass
-class ContestCombo(models.Model):
+class ContestCombo(PokeApiModel):
first_move = models.ForeignKey(
"Move",
blank=True,
@@ -1028,7 +1267,7 @@ class BerryFlavorName(IsName):
)
-class BerryFlavorMap(models.Model):
+class BerryFlavorMap(PokeApiModel):
berry = models.ForeignKey(Berry, blank=True, null=True, related_name="%(class)s", on_delete=models.CASCADE)
berry_flavor = models.ForeignKey(
@@ -1184,7 +1423,7 @@ class EncounterConditionValueName(IsName):
)
-class EncounterConditionValueMap(models.Model):
+class EncounterConditionValueMap(PokeApiModel):
encounter = models.ForeignKey(Encounter, blank=True, null=True, on_delete=models.CASCADE)
encounter_condition_value = models.ForeignKey(
@@ -1192,7 +1431,7 @@ class EncounterConditionValueMap(models.Model):
)
-class EncounterPokemonDetail(models.Model):
+class EncounterPokemonDetail(PokeApiModel):
encounter = models.ForeignKey(Encounter, blank=True, null=True, on_delete=models.CASCADE)
min_perfect_ivs = models.IntegerField(blank=True, null=True)
@@ -1290,7 +1529,7 @@ class MoveBattleStyleName(IsName):
########################
-class MoveEffect(models.Model):
+class MoveEffect(PokeApiModel):
pass
@@ -1489,7 +1728,7 @@ class PalPark(HasPokemonSpecies):
##########################
-class SuperContestEffect(models.Model):
+class SuperContestEffect(PokeApiModel):
appeal = models.IntegerField()
@@ -1497,7 +1736,7 @@ class SuperContestEffectFlavorText(IsFlavorText, HasSuperContestEffect):
pass
-class SuperContestCombo(models.Model):
+class SuperContestCombo(PokeApiModel):
first_move = models.ForeignKey(Move, blank=True, null=True, related_name="first", on_delete=models.CASCADE)
second_move = models.ForeignKey(Move, blank=True, null=True, related_name="second", on_delete=models.CASCADE)
@@ -1508,7 +1747,7 @@ class SuperContestCombo(models.Model):
######################
-class EvolutionChain(models.Model):
+class EvolutionChain(PokeApiModel):
baby_trigger_item = models.ForeignKey(Item, blank=True, null=True, on_delete=models.CASCADE)
@@ -1668,7 +1907,7 @@ class PokemonEvolution(HasEvolutionTrigger, HasGender):
held_item = models.ForeignKey(Item, blank=True, null=True, related_name="held_item", on_delete=models.CASCADE)
- time_of_day = models.CharField(max_length=10, blank=True, null=True)
+ time_of_day = models.CharField(max_length=10, blank=True, default="")
known_move = models.ForeignKey(Move, blank=True, null=True, on_delete=models.CASCADE)
@@ -1769,7 +2008,7 @@ class PokemonFormName(HasPokemonForm, IsName):
class PokemonFormSprites(HasPokemonForm):
- sprites = models.JSONField()
+ sprites: models.JSONField[Any] = models.JSONField()
class PokemonFormTrigger(HasName):
@@ -1873,8 +2112,8 @@ class PokemonTypePast(HasPokemon, HasType, HasGeneration):
class PokemonSprites(HasPokemon):
- sprites = models.JSONField()
+ sprites: models.JSONField[Any] = models.JSONField()
class PokemonCries(HasPokemon):
- cries = models.JSONField()
+ cries: models.JSONField[Any] = models.JSONField()
diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py
index ac841e9ff..430c1c385 100644
--- a/pokemon_v2/serializers.py
+++ b/pokemon_v2/serializers.py
@@ -1,14 +1,262 @@
-from collections import OrderedDict
-import json
-from django.urls import reverse
-from rest_framework import serializers
-from drf_spectacular.utils import extend_schema_field
+# ruff: noqa: F405
+# pyright: reportIncompatibleVariableOverride=false, reportUnknownMemberType=false
+from __future__ import annotations
-# pylint: disable=redefined-builtin
+import itertools
+from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast
-# PokeAPI v2 serializers in order of dependency
+from django.db.models import Q
+from drf_spectacular.utils import extend_schema_field # pyright: ignore[reportUnknownVariableType]
+from rest_framework import serializers
+from rest_framework.reverse import reverse
+
+from .models import * # noqa: F403
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from django.db import models
+ from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
+
+ class EncounterWithRelations(Protocol):
+ encounterconditionvaluemap_set: models.Manager[EncounterConditionValueMap]
+ encounterpokemondetail_set: models.Manager[EncounterPokemonDetail]
+
+ class PokemonWithRelations(Protocol):
+ pokemonsprites: models.Manager[PokemonSprites]
+ pokemoncries: models.Manager[PokemonCries]
+
+
+__all__: tuple[str, ...] = (
+ "AbilityChangeEffectTextSerializer",
+ "AbilityChangeSerializer",
+ "AbilityDetailSerializer",
+ "AbilityEffectTextSerializer",
+ "AbilityFlavorTextSerializer",
+ "AbilityNameSerializer",
+ "AbilityPokemonDetailSerializer",
+ "AbilitySummarySerializer",
+ "BerryDetailSerializer",
+ "BerryFirmnessDetailSerializer",
+ "BerryFirmnessNameSerializer",
+ "BerryFirmnessSummarySerializer",
+ "BerryFlavorBerryMapSerializer",
+ "BerryFlavorDetailSerializer",
+ "BerryFlavorMapSerializer",
+ "BerryFlavorNameSerializer",
+ "BerryFlavorSummarySerializer",
+ "BerrySummarySerializer",
+ "CharacteristicDescriptionSerializer",
+ "CharacteristicDetailSerializer",
+ "CharacteristicSummarySerializer",
+ "ContestEffectDetailSerializer",
+ "ContestEffectEffectTextSerializer",
+ "ContestEffectFlavorTextSerializer",
+ "ContestEffectSummarySerializer",
+ "ContestTypeDetailSerializer",
+ "ContestTypeNameSerializer",
+ "ContestTypeSummarySerializer",
+ "CurrencyDetailSerializer",
+ "CurrencyNameSerializer",
+ "CurrencySummarySerializer",
+ "EggGroupDetailSerializer",
+ "EggGroupNameSerializer",
+ "EggGroupSummarySerializer",
+ "EncounterConditionDetailSerializer",
+ "EncounterConditionNameSerializer",
+ "EncounterConditionSummarySerializer",
+ "EncounterConditionValueDetailSerializer",
+ "EncounterConditionValueMapSerializer",
+ "EncounterConditionValueNameSerializer",
+ "EncounterConditionValueSummarySerializer",
+ "EncounterDetailSerializer",
+ "EncounterMethodDetailSerializer",
+ "EncounterMethodNameSerializer",
+ "EncounterMethodSummarySerializer",
+ "EncounterPokemonDetailSerializer",
+ "EncounterSlotSerializer",
+ "EvolutionChainDetailSerializer",
+ "EvolutionChainLinkSerializer",
+ "EvolutionChainSummarySerializer",
+ "EvolutionTriggerDetailSerializer",
+ "EvolutionTriggerNameSerializer",
+ "EvolutionTriggerSummarySerializer",
+ "ExperienceSerializer",
+ "GenderDetailSerializer",
+ "GenderPokemonSpeciesSerializer",
+ "GenderSummarySerializer",
+ "GenerationDetailSerializer",
+ "GenerationNameSerializer",
+ "GenerationSummarySerializer",
+ "GrowthRateDescriptionSerializer",
+ "GrowthRateDetailSerializer",
+ "GrowthRateSummarySerializer",
+ "ItemAttributeDescriptionSerializer",
+ "ItemAttributeDetailSerializer",
+ "ItemAttributeNameSerializer",
+ "ItemAttributeSummarySerializer",
+ "ItemCategoryDetailSerializer",
+ "ItemCategoryNameSerializer",
+ "ItemCategorySummarySerializer",
+ "ItemDetailSerializer",
+ "ItemEffectTextSerializer",
+ "ItemFlavorTextSerializer",
+ "ItemFlingEffectDetailSerializer",
+ "ItemFlingEffectEffectTextSerializer",
+ "ItemFlingEffectSummarySerializer",
+ "ItemGameIndexSerializer",
+ "ItemMachineSerializer",
+ "ItemNameSerializer",
+ "ItemPocketDetailSerializer",
+ "ItemPocketNameSerializer",
+ "ItemPocketSummarySerializer",
+ "ItemPriceSerializer",
+ "ItemSpritesSerializer",
+ "ItemSummarySerializer",
+ "LanguageDetailSerializer",
+ "LanguageNameSerializer",
+ "LanguageSummarySerializer",
+ "LocationAreaDetailSerializer",
+ "LocationAreaEncounterDetailSerializer",
+ "LocationAreaEncounterRateSerializer",
+ "LocationAreaEncounterVersionDetailSerializer",
+ "LocationAreaNameSerializer",
+ "LocationAreaPokemonEncounterSerializer",
+ "LocationAreaPokemonEncounterVersionSerializer",
+ "LocationAreaSummarySerializer",
+ "LocationDetailSerializer",
+ "LocationGameIndexSerializer",
+ "LocationNameSerializer",
+ "LocationSummarySerializer",
+ "MachineDetailSerializer",
+ "MachineSummarySerializer",
+ "MoveBattleStyleDetailSerializer",
+ "MoveBattleStyleNameSerializer",
+ "MoveBattleStyleSummarySerializer",
+ "MoveChangeSerializer",
+ "MoveComboUsageSerializer",
+ "MoveCombosSerializer",
+ "MoveDamageClassDescriptionSerializer",
+ "MoveDamageClassDetailSerializer",
+ "MoveDamageClassNameSerializer",
+ "MoveDamageClassSummarySerializer",
+ "MoveDetailSerializer",
+ "MoveEffectChangeEffectTextSerializer",
+ "MoveEffectChangeSerializer",
+ "MoveEffectEffectTextSerializer",
+ "MoveFlavorTextSerializer",
+ "MoveLearnMethodDescriptionSerializer",
+ "MoveLearnMethodDetailSerializer",
+ "MoveLearnMethodNameSerializer",
+ "MoveLearnMethodSummarySerializer",
+ "MoveMetaAilmentDetailSerializer",
+ "MoveMetaAilmentNameSerializer",
+ "MoveMetaAilmentSummarySerializer",
+ "MoveMetaCategoryDescriptionSerializer",
+ "MoveMetaCategoryDetailSerializer",
+ "MoveMetaCategorySummarySerializer",
+ "MoveMetaSerializer",
+ "MoveMetaStatChangeSerializer",
+ "MoveNameSerializer",
+ "MoveStatChangeSerializer",
+ "MoveSummarySerializer",
+ "MoveTargetDescriptionSerializer",
+ "MoveTargetDetailSerializer",
+ "MoveTargetNameSerializer",
+ "MoveTargetSummarySerializer",
+ "NatureBattleStylePreferenceSerializer",
+ "NatureDetailSerializer",
+ "NatureNameSerializer",
+ "NaturePokeathlonStatSerializer",
+ "NatureSummarySerializer",
+ "PalParkAreaDetailSerializer",
+ "PalParkAreaNameSerializer",
+ "PalParkAreaSummarySerializer",
+ "PalParkEncounterSerializer",
+ "PokeathlonStatAffectingNatureSerializer",
+ "PokeathlonStatAffectingNaturesSerializer",
+ "PokeathlonStatDetailSerializer",
+ "PokeathlonStatNameSerializer",
+ "PokeathlonStatSummarySerializer",
+ "PokedexDescriptionSerializer",
+ "PokedexDetailSerializer",
+ "PokedexNameSerializer",
+ "PokedexSummarySerializer",
+ "PokemonAbilityPastSerializer",
+ "PokemonAbilitySerializer",
+ "PokemonColorDetailSerializer",
+ "PokemonColorNameSerializer",
+ "PokemonColorSummarySerializer",
+ "PokemonCriesSerializer",
+ "PokemonDetailSerializer",
+ "PokemonDexEntrySerializer",
+ "PokemonDexNumberSerializer",
+ "PokemonEvolutionSerializer",
+ "PokemonFormConditionSerializer",
+ "PokemonFormDetailSerializer",
+ "PokemonFormNameSerializer",
+ "PokemonFormSpritesSerializer",
+ "PokemonFormSummarySerializer",
+ "PokemonFormTriggerConditionSerializer",
+ "PokemonFormTypeSerializer",
+ "PokemonGameIndexSerializer",
+ "PokemonHabitatDetailSerializer",
+ "PokemonHabitatNameSerializer",
+ "PokemonHabitatSummarySerializer",
+ "PokemonHeldItemSerializer",
+ "PokemonHeldItemVersionSerializer",
+ "PokemonMoveSerializer",
+ "PokemonMoveVersionGroupSerializer",
+ "PokemonPastAbilitySerializer",
+ "PokemonPastStatSerializer",
+ "PokemonPastTypeSerializer",
+ "PokemonShapeAwesomeNameSerializer",
+ "PokemonShapeDetailSerializer",
+ "PokemonShapeNameSerializer",
+ "PokemonShapeSummarySerializer",
+ "PokemonSpeciesDescriptionSerializer",
+ "PokemonSpeciesDetailSerializer",
+ "PokemonSpeciesEvolutionSerializer",
+ "PokemonSpeciesFlavorTextSerializer",
+ "PokemonSpeciesGenusSerializer",
+ "PokemonSpeciesNameSerializer",
+ "PokemonSpeciesPalParkEncounterSerializer",
+ "PokemonSpeciesSummarySerializer",
+ "PokemonSpeciesVarietySerializer",
+ "PokemonSpritesSerializer",
+ "PokemonStatPastSerializer",
+ "PokemonStatSerializer",
+ "PokemonSummarySerializer",
+ "PokemonTypePastSerializer",
+ "PokemonTypeSerializer",
+ "RegionDetailSerializer",
+ "RegionNameSerializer",
+ "RegionSummarySerializer",
+ "StatAffectingMovesSerializer",
+ "StatAffectingNaturesSerializer",
+ "StatDetailSerializer",
+ "StatNameSerializer",
+ "StatSummarySerializer",
+ "SuperContestEffectDetailSerializer",
+ "SuperContestEffectFlavorTextSerializer",
+ "SuperContestEffectSummarySerializer",
+ "TypeDetailSerializer",
+ "TypeEfficacyPastSerializer",
+ "TypeGameIndexSerializer",
+ "TypeNameSerializer",
+ "TypePastRelationshipsSerializer",
+ "TypePokemonSerializer",
+ "TypeRelationshipsSerializer",
+ "TypeSpriteSerializer",
+ "TypeSummarySerializer",
+ "VersionDetailSerializer",
+ "VersionGroupDetailSerializer",
+ "VersionGroupSummarySerializer",
+ "VersionNameSerializer",
+ "VersionSummarySerializer",
+)
-from .models import *
+# PokeAPI v2 serializers in order of dependency
#########################
# SUMMARY SERIALIZERS #
@@ -20,295 +268,295 @@
# with reference accross models due to script running order
-class AbilitySummarySerializer(serializers.HyperlinkedModelSerializer):
+class AbilitySummarySerializer(serializers.HyperlinkedModelSerializer[Ability]):
class Meta:
model = Ability
fields = ("name", "url")
-class BerryFirmnessSummarySerializer(serializers.HyperlinkedModelSerializer):
+class BerryFirmnessSummarySerializer(serializers.HyperlinkedModelSerializer[BerryFirmness]):
class Meta:
model = BerryFirmness
fields = ("name", "url")
-class BerryFlavorSummarySerializer(serializers.HyperlinkedModelSerializer):
+class BerryFlavorSummarySerializer(serializers.HyperlinkedModelSerializer[BerryFlavor]):
class Meta:
model = BerryFlavor
fields = ("name", "url")
-class BerrySummarySerializer(serializers.HyperlinkedModelSerializer):
+class BerrySummarySerializer(serializers.HyperlinkedModelSerializer[Berry]):
class Meta:
model = Berry
fields = ("name", "url")
-class CharacteristicSummarySerializer(serializers.HyperlinkedModelSerializer):
+class CharacteristicSummarySerializer(serializers.HyperlinkedModelSerializer[Characteristic]):
class Meta:
model = Characteristic
fields = ("url",)
-class ContestEffectSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ContestEffectSummarySerializer(serializers.HyperlinkedModelSerializer[ContestEffect]):
class Meta:
model = ContestEffect
fields = ("url",)
-class ContestTypeSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ContestTypeSummarySerializer(serializers.HyperlinkedModelSerializer[ContestType]):
class Meta:
model = ContestType
fields = ("name", "url")
-class EggGroupSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EggGroupSummarySerializer(serializers.HyperlinkedModelSerializer[EggGroup]):
class Meta:
model = EggGroup
fields = ("name", "url")
-class EncounterConditionSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EncounterConditionSummarySerializer(serializers.HyperlinkedModelSerializer[EncounterCondition]):
class Meta:
model = EncounterCondition
fields = ("name", "url")
-class EncounterConditionValueSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EncounterConditionValueSummarySerializer(serializers.HyperlinkedModelSerializer[EncounterConditionValue]):
class Meta:
model = EncounterConditionValue
fields = ("name", "url")
-class EncounterMethodSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EncounterMethodSummarySerializer(serializers.HyperlinkedModelSerializer[EncounterMethod]):
class Meta:
model = EncounterMethod
fields = ("name", "url")
-class EvolutionTriggerSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EvolutionTriggerSummarySerializer(serializers.HyperlinkedModelSerializer[EvolutionTrigger]):
class Meta:
model = EvolutionTrigger
fields = ("name", "url")
-class EvolutionChainSummarySerializer(serializers.HyperlinkedModelSerializer):
+class EvolutionChainSummarySerializer(serializers.HyperlinkedModelSerializer[EvolutionChain]):
class Meta:
model = EvolutionChain
fields = ("url",)
-class GenerationSummarySerializer(serializers.HyperlinkedModelSerializer):
+class GenerationSummarySerializer(serializers.HyperlinkedModelSerializer[Generation]):
class Meta:
model = Generation
fields = ("name", "url")
-class GenderSummarySerializer(serializers.HyperlinkedModelSerializer):
+class GenderSummarySerializer(serializers.HyperlinkedModelSerializer[Gender]):
class Meta:
model = Gender
fields = ("name", "url")
-class GrowthRateSummarySerializer(serializers.HyperlinkedModelSerializer):
+class GrowthRateSummarySerializer(serializers.HyperlinkedModelSerializer[GrowthRate]):
class Meta:
model = GrowthRate
fields = ("name", "url")
-class CurrencySummarySerializer(serializers.HyperlinkedModelSerializer):
+class CurrencySummarySerializer(serializers.HyperlinkedModelSerializer[Currency]):
class Meta:
model = Currency
fields = ("name", "url")
-class ItemPocketSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ItemPocketSummarySerializer(serializers.HyperlinkedModelSerializer[ItemPocket]):
class Meta:
model = ItemPocket
fields = ("name", "url")
-class ItemCategorySummarySerializer(serializers.HyperlinkedModelSerializer):
+class ItemCategorySummarySerializer(serializers.HyperlinkedModelSerializer[ItemCategory]):
class Meta:
model = ItemCategory
fields = ("name", "url")
-class ItemAttributeSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ItemAttributeSummarySerializer(serializers.HyperlinkedModelSerializer[ItemAttribute]):
class Meta:
model = ItemAttribute
fields = ("name", "url")
-class ItemFlingEffectSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ItemFlingEffectSummarySerializer(serializers.HyperlinkedModelSerializer[ItemFlingEffect]):
class Meta:
model = ItemFlingEffect
fields = ("name", "url")
-class ItemSummarySerializer(serializers.HyperlinkedModelSerializer):
+class ItemSummarySerializer(serializers.HyperlinkedModelSerializer[Item]):
class Meta:
model = Item
fields = ("name", "url")
-class LanguageSummarySerializer(serializers.HyperlinkedModelSerializer):
+class LanguageSummarySerializer(serializers.HyperlinkedModelSerializer[Language]):
class Meta:
model = Language
fields = ("name", "url")
-class LocationSummarySerializer(serializers.HyperlinkedModelSerializer):
+class LocationSummarySerializer(serializers.HyperlinkedModelSerializer[Location]):
class Meta:
model = Location
fields = ("name", "url")
-class LocationAreaSummarySerializer(serializers.HyperlinkedModelSerializer):
+class LocationAreaSummarySerializer(serializers.HyperlinkedModelSerializer[LocationArea]):
class Meta:
model = LocationArea
fields = ("name", "url")
-class MachineSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MachineSummarySerializer(serializers.HyperlinkedModelSerializer[Machine]):
class Meta:
model = Machine
fields = ("url",)
-class MoveBattleStyleSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveBattleStyleSummarySerializer(serializers.HyperlinkedModelSerializer[MoveBattleStyle]):
class Meta:
model = MoveBattleStyle
fields = ("name", "url")
-class MoveDamageClassSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveDamageClassSummarySerializer(serializers.HyperlinkedModelSerializer[MoveDamageClass]):
class Meta:
model = MoveDamageClass
fields = ("name", "url")
-class MoveMetaAilmentSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveMetaAilmentSummarySerializer(serializers.HyperlinkedModelSerializer[MoveMetaAilment]):
class Meta:
model = MoveMetaAilment
fields = ("name", "url")
-class MoveMetaCategorySummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveMetaCategorySummarySerializer(serializers.HyperlinkedModelSerializer[MoveMetaCategory]):
class Meta:
model = MoveMetaCategory
fields = ("name", "url")
-class MoveTargetSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveTargetSummarySerializer(serializers.HyperlinkedModelSerializer[MoveTarget]):
class Meta:
model = MoveTarget
fields = ("name", "url")
-class MoveSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveSummarySerializer(serializers.HyperlinkedModelSerializer[Move]):
class Meta:
model = Move
fields = ("name", "url")
-class MoveLearnMethodSummarySerializer(serializers.HyperlinkedModelSerializer):
+class MoveLearnMethodSummarySerializer(serializers.HyperlinkedModelSerializer[MoveLearnMethod]):
class Meta:
model = MoveLearnMethod
fields = ("name", "url")
-class NatureSummarySerializer(serializers.HyperlinkedModelSerializer):
+class NatureSummarySerializer(serializers.HyperlinkedModelSerializer[Nature]):
class Meta:
model = Nature
fields = ("name", "url")
-class PalParkAreaSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PalParkAreaSummarySerializer(serializers.HyperlinkedModelSerializer[PalParkArea]):
class Meta:
model = PalParkArea
fields = ("name", "url")
-class PokeathlonStatSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokeathlonStatSummarySerializer(serializers.HyperlinkedModelSerializer[PokeathlonStat]):
class Meta:
model = PokeathlonStat
fields = ("name", "url")
-class PokedexSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokedexSummarySerializer(serializers.HyperlinkedModelSerializer[Pokedex]):
class Meta:
model = Pokedex
fields = ("name", "url")
-class PokemonColorSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonColorSummarySerializer(serializers.HyperlinkedModelSerializer[PokemonColor]):
class Meta:
model = PokemonColor
fields = ("name", "url")
-class PokemonHabitatSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonHabitatSummarySerializer(serializers.HyperlinkedModelSerializer[PokemonHabitat]):
class Meta:
model = PokemonHabitat
fields = ("name", "url")
-class PokemonShapeSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonShapeSummarySerializer(serializers.HyperlinkedModelSerializer[PokemonShape]):
class Meta:
model = PokemonShape
fields = ("name", "url")
-class PokemonSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonSummarySerializer(serializers.HyperlinkedModelSerializer[Pokemon]):
class Meta:
model = Pokemon
fields = ("name", "url")
-class PokemonSpeciesSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonSpeciesSummarySerializer(serializers.HyperlinkedModelSerializer[PokemonSpecies]):
class Meta:
model = PokemonSpecies
fields = ("name", "url")
-class PokemonFormSummarySerializer(serializers.HyperlinkedModelSerializer):
+class PokemonFormSummarySerializer(serializers.HyperlinkedModelSerializer[PokemonForm]):
class Meta:
model = PokemonForm
fields = ("name", "url")
-class RegionSummarySerializer(serializers.HyperlinkedModelSerializer):
+class RegionSummarySerializer(serializers.HyperlinkedModelSerializer[Region]):
class Meta:
model = Region
fields = ("name", "url")
-class StatSummarySerializer(serializers.HyperlinkedModelSerializer):
+class StatSummarySerializer(serializers.HyperlinkedModelSerializer[Stat]):
class Meta:
model = Stat
fields = ("name", "url")
-class SuperContestEffectSummarySerializer(serializers.HyperlinkedModelSerializer):
+class SuperContestEffectSummarySerializer(serializers.HyperlinkedModelSerializer[SuperContestEffect]):
class Meta:
model = SuperContestEffect
fields = ("url",)
-class TypeSummarySerializer(serializers.HyperlinkedModelSerializer):
+class TypeSummarySerializer(serializers.HyperlinkedModelSerializer[Type]):
class Meta:
model = Type
fields = ("name", "url")
-class VersionSummarySerializer(serializers.HyperlinkedModelSerializer):
+class VersionSummarySerializer(serializers.HyperlinkedModelSerializer[Version]):
class Meta:
model = Version
fields = ("name", "url")
-class VersionGroupSummarySerializer(serializers.HyperlinkedModelSerializer):
+class VersionGroupSummarySerializer(serializers.HyperlinkedModelSerializer[VersionGroup]):
class Meta:
model = VersionGroup
fields = ("name", "url")
@@ -319,64 +567,7 @@ class Meta:
#####################
-class BerryFlavorMapSerializer(serializers.ModelSerializer):
- berry = BerrySummarySerializer()
- flavor = BerryFlavorSummarySerializer(source="berry_flavor")
-
- class Meta:
- model = BerryFlavorMap
- fields = ("potency", "berry", "flavor")
-
-
-class ItemAttributeMapSerializer(serializers.ModelSerializer):
- item = ItemSummarySerializer()
- attribute = ItemAttributeSummarySerializer(source="item_attribute")
-
- class Meta:
- model = ItemAttributeMap
- fields = (
- "item",
- "attribute",
- )
-
-
-class MoveMetaStatChangeSerializer(serializers.ModelSerializer):
- stat = StatSummarySerializer()
- move = MoveSummarySerializer()
-
- class Meta:
- model = MoveMetaStatChange
- fields = ("change", "move", "stat")
-
-
-class NaturePokeathlonStatSerializer(serializers.ModelSerializer):
- pokeathlon_stat = PokeathlonStatSummarySerializer()
- nature = NatureSummarySerializer()
-
- class Meta:
- model = NaturePokeathlonStat
- fields = ("max_change", "nature", "pokeathlon_stat")
-
-
-class PokemonAbilitySerializer(serializers.ModelSerializer):
- pokemon = PokemonSummarySerializer()
- ability = AbilitySummarySerializer()
-
- class Meta:
- model = PokemonAbility
- fields = ("is_hidden", "slot", "ability", "pokemon")
-
-
-class PokemonAbilityPastSerializer(serializers.ModelSerializer):
- generation = GenerationSummarySerializer()
- ability = AbilitySummarySerializer()
-
- class Meta:
- model = PokemonAbilityPast
- fields = ("is_hidden", "pokemon", "generation", "slot", "ability")
-
-
-class PokemonDexEntrySerializer(serializers.ModelSerializer):
+class PokemonDexEntrySerializer(serializers.ModelSerializer[PokemonDexNumber]):
entry_number = serializers.IntegerField(source="pokedex_number")
pokedex = PokedexSummarySerializer()
@@ -385,64 +576,7 @@ class Meta:
fields = ("entry_number", "pokedex")
-class PokemonTypeSerializer(serializers.ModelSerializer):
- pokemon = PokemonSummarySerializer()
- type = TypeSummarySerializer()
-
- class Meta:
- model = PokemonType
- fields = ("slot", "pokemon", "type")
-
-
-class PokemonFormTypeSerializer(serializers.ModelSerializer):
- pokemon_form = PokemonFormSummarySerializer()
- type = TypeSummarySerializer()
-
- class Meta:
- model = PokemonFormType
- fields = ("slot", "pokemon_form", "type")
-
-
-class PokemonTypePastSerializer(serializers.ModelSerializer):
- generation = GenerationSummarySerializer()
- type = TypeSummarySerializer()
-
- class Meta:
- model = PokemonTypePast
- fields = ("pokemon", "generation", "slot", "type")
-
-
-class PokedexVersionGroupSerializer(serializers.ModelSerializer):
- pokedex = PokedexSummarySerializer()
- version_group = VersionGroupSummarySerializer()
-
- class Meta:
- model = PokedexVersionGroup
- fields = ("pokedex", "version_group")
-
-
-class VersionGroupMoveLearnMethodSerializer(serializers.ModelSerializer):
- version_group = VersionGroupSummarySerializer()
- move_learn_method = MoveLearnMethodSummarySerializer()
-
- class Meta:
- model = ItemAttributeMap
- fields = ("version_group", "move_learn_method")
-
-
-class VersionGroupRegionSerializer(serializers.ModelSerializer):
- version_group = VersionGroupSummarySerializer()
- region = RegionSummarySerializer()
-
- class Meta:
- model = ItemAttributeMap
- fields = (
- "version_group",
- "region",
- )
-
-
-class EncounterConditionValueMapSerializer(serializers.ModelSerializer):
+class EncounterConditionValueMapSerializer(serializers.ModelSerializer[EncounterConditionValueMap]):
condition_value = EncounterConditionValueSummarySerializer(source="encounter_condition_value")
class Meta:
@@ -455,7 +589,7 @@ class Meta:
################################
-class CharacteristicDescriptionSerializer(serializers.ModelSerializer):
+class CharacteristicDescriptionSerializer(serializers.ModelSerializer[CharacteristicDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -463,7 +597,7 @@ class Meta:
fields = ("description", "language")
-class CharacteristicDetailSerializer(serializers.ModelSerializer):
+class CharacteristicDetailSerializer(serializers.ModelSerializer[Characteristic]):
descriptions = CharacteristicDescriptionSerializer(many=True, read_only=True, source="characteristicdescription")
highest_stat = StatSummarySerializer(source="stat")
gene_modulo = serializers.IntegerField(source="gene_mod_5")
@@ -479,24 +613,9 @@ class Meta:
"descriptions",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "integer",
- "format": "int32",
- },
- "examples": [[0, 5, 10, 15, 20, 25, 30]],
- }
- )
- def get_values(self, obj):
- mod = obj.gene_mod_5
- values = []
- while mod <= 31:
- values.append(mod)
- mod += 5
-
- return values
+ @extend_schema_field(serializers.ListField(child=serializers.IntegerField()))
+ def get_values(self, obj: Characteristic) -> list[int]:
+ return list(range(obj.gene_mod_5, 32, 5))
#########################
@@ -504,7 +623,7 @@ def get_values(self, obj):
#########################
-class SuperContestEffectFlavorTextSerializer(serializers.ModelSerializer):
+class SuperContestEffectFlavorTextSerializer(serializers.ModelSerializer[SuperContestEffectFlavorText]):
language = LanguageSummarySerializer()
class Meta:
@@ -512,7 +631,7 @@ class Meta:
fields = ("flavor_text", "language")
-class SuperContestEffectDetailSerializer(serializers.ModelSerializer):
+class SuperContestEffectDetailSerializer(serializers.ModelSerializer[SuperContestEffect]):
flavor_text_entries = SuperContestEffectFlavorTextSerializer(
many=True, read_only=True, source="supercontesteffectflavortext"
)
@@ -523,7 +642,7 @@ class Meta:
fields = ("id", "appeal", "flavor_text_entries", "moves")
-class ContestEffectEffectTextSerializer(serializers.ModelSerializer):
+class ContestEffectEffectTextSerializer(serializers.ModelSerializer[ContestEffectEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -531,7 +650,7 @@ class Meta:
fields = ("effect", "language")
-class ContestEffectFlavorTextSerializer(serializers.ModelSerializer):
+class ContestEffectFlavorTextSerializer(serializers.ModelSerializer[ContestEffectFlavorText]):
language = LanguageSummarySerializer()
class Meta:
@@ -539,7 +658,7 @@ class Meta:
fields = ("flavor_text", "language")
-class ContestEffectDetailSerializer(serializers.ModelSerializer):
+class ContestEffectDetailSerializer(serializers.ModelSerializer[ContestEffect]):
effect_entries = ContestEffectEffectTextSerializer(many=True, read_only=True, source="contesteffecteffecttext")
flavor_text_entries = ContestEffectFlavorTextSerializer(many=True, read_only=True, source="contesteffectflavortext")
@@ -548,7 +667,7 @@ class Meta:
fields = ("id", "appeal", "jam", "effect_entries", "flavor_text_entries")
-class ContestTypeNameSerializer(serializers.ModelSerializer):
+class ContestTypeNameSerializer(serializers.ModelSerializer[ContestTypeName]):
language = LanguageSummarySerializer()
class Meta:
@@ -556,7 +675,7 @@ class Meta:
fields = ("name", "color", "language")
-class ContestTypeDetailSerializer(serializers.ModelSerializer):
+class ContestTypeDetailSerializer(serializers.ModelSerializer[ContestType]):
names = ContestTypeNameSerializer(many=True, read_only=True, source="contesttypename")
berry_flavor = BerryFlavorSummarySerializer(read_only=True, source="berryflavor")
@@ -565,30 +684,12 @@ class Meta:
fields = ("id", "name", "berry_flavor", "names")
-class SuperContestComboSerializer(serializers.ModelSerializer):
- first_move = MoveSummarySerializer()
- second_move = MoveSummarySerializer()
-
- class Meta:
- model = SuperContestCombo
- fields = ("first_move", "second_move")
-
-
-class ContestComboSerializer(serializers.ModelSerializer):
- first_move = MoveSummarySerializer()
- second_move = MoveSummarySerializer()
-
- class Meta:
- model = ContestCombo
- fields = ("first_move", "second_move")
-
-
########################
# REGION SERIALIZERS #
########################
-class RegionNameSerializer(serializers.ModelSerializer):
+class RegionNameSerializer(serializers.ModelSerializer[RegionName]):
language = LanguageSummarySerializer()
class Meta:
@@ -596,7 +697,7 @@ class Meta:
fields = ("name", "language")
-class RegionDetailSerializer(serializers.ModelSerializer):
+class RegionDetailSerializer(serializers.ModelSerializer[Region]):
names = RegionNameSerializer(many=True, read_only=True, source="regionname")
locations = LocationSummarySerializer(many=True, read_only=True, source="location")
version_groups = serializers.SerializerMethodField("get_region_version_groups")
@@ -615,32 +716,13 @@ class Meta:
"version_groups",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["red-blue"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/1/"],
- },
- },
- },
- }
- )
- def get_region_version_groups(self, obj):
- vg_regions = VersionGroupRegion.objects.filter(region=obj)
- data = VersionGroupRegionSerializer(vg_regions, many=True, context=self.context).data
- groups = []
-
- for group in data:
- groups.append(group["version_group"])
-
- return groups
+ @extend_schema_field(VersionGroupSummarySerializer(many=True))
+ def get_region_version_groups(self, obj: Region) -> ReturnList[ReturnDict[str, Any]]:
+ version_groups = VersionGroup.objects.filter(versiongroupregion__region=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ VersionGroupSummarySerializer(version_groups, many=True, context=self.context).data,
+ )
############################
@@ -648,7 +730,7 @@ def get_region_version_groups(self, obj):
############################
-class GenerationNameSerializer(serializers.ModelSerializer):
+class GenerationNameSerializer(serializers.ModelSerializer[GenerationName]):
language = LanguageSummarySerializer()
class Meta:
@@ -656,7 +738,7 @@ class Meta:
fields = ("name", "language")
-class GenerationDetailSerializer(serializers.ModelSerializer):
+class GenerationDetailSerializer(serializers.ModelSerializer[Generation]):
main_region = RegionSummarySerializer(source="region")
names = GenerationNameSerializer(many=True, read_only=True, source="generationname")
abilities = AbilitySummarySerializer(many=True, read_only=True, source="ability")
@@ -685,7 +767,16 @@ class Meta:
########################
-class GenderDetailSerializer(serializers.ModelSerializer):
+class GenderPokemonSpeciesSerializer(serializers.ModelSerializer[PokemonSpecies]):
+ rate = serializers.IntegerField(source="gender_rate")
+ pokemon_species = PokemonSpeciesSummarySerializer(source="*")
+
+ class Meta:
+ model = PokemonSpecies
+ fields = ("rate", "pokemon_species")
+
+
+class GenderDetailSerializer(serializers.ModelSerializer[Gender]):
pokemon_species_details = serializers.SerializerMethodField("get_species")
required_for_evolution = serializers.SerializerMethodField("get_required")
@@ -693,76 +784,26 @@ class Meta:
model = Gender
fields = ("id", "name", "pokemon_species_details", "required_for_evolution")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["rate", "pokemon_species"],
- "properties": {
- "rate": {"type": "integer", "format": "int32", "examples": [1]},
- "pokemon_species": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bulbasaur"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/1/"],
- },
- },
- },
- },
- },
- }
- )
- def get_species(self, obj):
- species_objects = []
-
- if obj.name == "female":
- species_objects = PokemonSpecies.objects.filter(gender_rate__gt=0)
- elif obj.name == "male":
- species_objects = PokemonSpecies.objects.filter(gender_rate__range=[0, 7])
- elif obj.name == "genderless":
- species_objects = PokemonSpecies.objects.filter(gender_rate=-1)
-
- details = []
-
- for species in species_objects:
- detail = OrderedDict()
- detail["rate"] = species.gender_rate
- detail["pokemon_species"] = PokemonSpeciesSummarySerializer(species, context=self.context).data
- details.append(detail)
-
- return details
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["wormadam"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/413/"],
- },
- },
- },
- }
- )
- def get_required(self, obj):
- evo_objects = PokemonEvolution.objects.filter(gender=obj)
- species_list = []
-
- for evo in evo_objects:
- species = PokemonSpeciesSummarySerializer(evo.evolved_species, context=self.context).data
- species_list.append(species)
+ @extend_schema_field(GenderPokemonSpeciesSerializer(many=True))
+ def get_species(self, obj: Gender) -> ReturnList[ReturnDict[str, Any]]:
+ gender_filters = {
+ "female": Q(gender_rate__gt=0),
+ "male": Q(gender_rate__range=[0, 7]),
+ "genderless": Q(gender_rate=-1),
+ }
+ species_objects = PokemonSpecies.objects.filter(gender_filters.get(obj.name, Q(pk__in=[])))
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ GenderPokemonSpeciesSerializer(species_objects, many=True, context=self.context).data,
+ )
- return species_list
+ @extend_schema_field(PokemonSpeciesSummarySerializer(many=True))
+ def get_required(self, obj: Gender) -> ReturnList[ReturnDict[str, Any]]:
+ species = PokemonSpecies.objects.filter(evolved_species__gender=obj).distinct().order_by("id")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesSummarySerializer(species, many=True, context=self.context).data,
+ )
#############################
@@ -770,13 +811,13 @@ def get_required(self, obj):
#############################
-class ExperienceSerializer(serializers.ModelSerializer):
+class ExperienceSerializer(serializers.ModelSerializer[Experience]):
class Meta:
model = Experience
fields = ("level", "experience")
-class GrowthRateDescriptionSerializer(serializers.ModelSerializer):
+class GrowthRateDescriptionSerializer(serializers.ModelSerializer[GrowthRateDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -784,7 +825,7 @@ class Meta:
fields = ("description", "language")
-class GrowthRateDetailSerializer(serializers.ModelSerializer):
+class GrowthRateDetailSerializer(serializers.ModelSerializer[GrowthRate]):
descriptions = GrowthRateDescriptionSerializer(many=True, read_only=True, source="growthratedescription")
levels = ExperienceSerializer(many=True, read_only=True, source="experience")
pokemon_species = PokemonSpeciesSummarySerializer(many=True, read_only=True, source="pokemonspecies")
@@ -799,7 +840,7 @@ class Meta:
##########################
-class LanguageNameSerializer(serializers.ModelSerializer):
+class LanguageNameSerializer(serializers.ModelSerializer[LanguageName]):
language = LanguageSummarySerializer(source="local_language")
class Meta:
@@ -807,7 +848,7 @@ class Meta:
fields = ("name", "language")
-class LanguageDetailSerializer(serializers.ModelSerializer):
+class LanguageDetailSerializer(serializers.ModelSerializer[Language]):
names = LanguageNameSerializer(many=True, read_only=True, source="languagename_language")
class Meta:
@@ -820,7 +861,7 @@ class Meta:
########################################
-class EncounterConditionNameSerializer(serializers.ModelSerializer):
+class EncounterConditionNameSerializer(serializers.ModelSerializer[EncounterConditionName]):
language = LanguageSummarySerializer()
class Meta:
@@ -828,7 +869,7 @@ class Meta:
fields = ("name", "language")
-class EncounterConditionDetailSerializer(serializers.ModelSerializer):
+class EncounterConditionDetailSerializer(serializers.ModelSerializer[EncounterCondition]):
names = EncounterConditionNameSerializer(many=True, read_only=True, source="encounterconditionname")
values = EncounterConditionValueSummarySerializer(many=True, read_only=True, source="encounterconditionvalue")
@@ -837,7 +878,7 @@ class Meta:
fields = ("id", "name", "values", "names")
-class EncounterConditionValueNameSerializer(serializers.ModelSerializer):
+class EncounterConditionValueNameSerializer(serializers.ModelSerializer[EncounterConditionValueName]):
language = LanguageSummarySerializer()
class Meta:
@@ -845,7 +886,7 @@ class Meta:
fields = ("name", "language")
-class EncounterConditionValueDetailSerializer(serializers.ModelSerializer):
+class EncounterConditionValueDetailSerializer(serializers.ModelSerializer[EncounterConditionValue]):
condition = EncounterConditionSummarySerializer(source="encounter_condition")
names = EncounterConditionValueNameSerializer(many=True, read_only=True, source="encounterconditionvaluename")
@@ -854,7 +895,7 @@ class Meta:
fields = ("id", "name", "condition", "names")
-class EncounterMethodNameSerializer(serializers.ModelSerializer):
+class EncounterMethodNameSerializer(serializers.ModelSerializer[EncounterMethodName]):
language = LanguageSummarySerializer()
class Meta:
@@ -862,7 +903,7 @@ class Meta:
fields = ("name", "language")
-class EncounterMethodDetailSerializer(serializers.ModelSerializer):
+class EncounterMethodDetailSerializer(serializers.ModelSerializer[EncounterMethod]):
names = EncounterMethodNameSerializer(many=True, read_only=True, source="encountermethodname")
class Meta:
@@ -870,7 +911,7 @@ class Meta:
fields = ("id", "name", "order", "names")
-class EncounterSlotSerializer(serializers.ModelSerializer):
+class EncounterSlotSerializer(serializers.ModelSerializer[EncounterSlot]):
encounter_method = EncounterMethodSummarySerializer()
chance = serializers.IntegerField(source="rarity")
@@ -879,7 +920,7 @@ class Meta:
fields = ("id", "slot", "chance", "encounter_method", "version_group")
-class EncounterPokemonDetailSerializer(serializers.ModelSerializer):
+class EncounterPokemonDetailSerializer(serializers.ModelSerializer[EncounterPokemonDetail]):
class Meta:
model = EncounterPokemonDetail
fields = (
@@ -890,7 +931,7 @@ class Meta:
)
-class EncounterDetailSerializer(serializers.ModelSerializer):
+class EncounterDetailSerializer(serializers.ModelSerializer[Encounter]):
version = VersionSummarySerializer()
location_area = LocationAreaSummarySerializer()
pokemon = PokemonSummarySerializer()
@@ -910,43 +951,85 @@ class Meta:
"pokemon_details",
)
- def get_encounter_conditions(self, obj):
+ @extend_schema_field(EncounterConditionValueSummarySerializer(many=True))
+ def get_encounter_conditions(self, obj: Encounter) -> list[dict[str, Any]]:
condition_values = EncounterConditionValueMap.objects.filter(encounter=obj)
- data = EncounterConditionValueMapSerializer(condition_values, many=True, context=self.context).data
- values = []
+ data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ EncounterConditionValueMapSerializer(condition_values, many=True, context=self.context).data,
+ )
+ return [item["condition_value"] for item in data]
+
+ @extend_schema_field(EncounterPokemonDetailSerializer(allow_null=True))
+ def get_encounter_pokemon_details(self, obj: Encounter) -> ReturnDict[str, Any] | None:
+ encounter_pokemon_details = EncounterPokemonDetail.objects.filter(encounter=obj).first()
+ return cast(
+ "ReturnDict[str, Any] | None",
+ EncounterPokemonDetailSerializer(encounter_pokemon_details, context=self.context).data
+ if encounter_pokemon_details
+ else None,
+ )
- for map in data:
- values.append(map["condition_value"])
- return values
+class LocationAreaNameSerializer(serializers.ModelSerializer[LocationAreaName]):
+ language = LanguageSummarySerializer()
- def get_encounter_pokemon_details(self, obj):
- encounter_pokemon_details = EncounterPokemonDetail.objects.filter(encounter=obj)
- data = EncounterPokemonDetailSerializer(encounter_pokemon_details, many=True, context=self.context).data
+ class Meta:
+ model = LocationAreaName
+ fields = ("name", "language")
- pokemon_details = data[0] if len(data) else None
- return pokemon_details
+class LocationAreaEncounterVersionDetailSerializer(serializers.Serializer[Any]):
+ rate = serializers.IntegerField()
+ version = VersionSummarySerializer()
-class LocationAreaEncounterRateSerializer(serializers.ModelSerializer):
+class LocationAreaEncounterRateSerializer(serializers.Serializer[Any]):
encounter_method = EncounterMethodSummarySerializer()
- version = VersionSummarySerializer()
+ version_details = LocationAreaEncounterVersionDetailSerializer(many=True)
- class Meta:
- model = LocationAreaEncounterRate
- fields = ("rate", "encounter_method", "version")
+class LocationAreaEncounterDetailSerializer(serializers.Serializer[Any]):
+ min_level = serializers.IntegerField()
+ max_level = serializers.IntegerField()
+ chance = serializers.IntegerField(source="encounter_slot.rarity", default=0)
+ method = EncounterMethodSummarySerializer(source="encounter_slot.encounter_method", default=None)
+ condition_values = serializers.SerializerMethodField("get_encounter_conditions")
+ pokemon_details = serializers.SerializerMethodField("get_encounter_pokemon_details")
-class LocationAreaNameSerializer(serializers.ModelSerializer):
- language = LanguageSummarySerializer()
+ @extend_schema_field(EncounterConditionValueSummarySerializer(many=True))
+ def get_encounter_conditions(self, obj: Encounter) -> list[ReturnDict[str, Any]]:
+ condition_maps = cast("EncounterWithRelations", obj).encounterconditionvaluemap_set.all()
+ return [
+ cast(
+ "ReturnDict[str, Any]",
+ EncounterConditionValueSummarySerializer(cv.encounter_condition_value, context=self.context).data,
+ )
+ for cv in condition_maps
+ ]
+
+ @extend_schema_field(EncounterPokemonDetailSerializer(allow_null=True))
+ def get_encounter_pokemon_details(self, obj: Encounter) -> ReturnDict[str, Any] | None:
+ details_list = list(cast("EncounterWithRelations", obj).encounterpokemondetail_set.all())
+ details = details_list[0] if details_list else None
+ return cast(
+ "ReturnDict[str, Any] | None",
+ EncounterPokemonDetailSerializer(details, context=self.context).data if details else None,
+ )
- class Meta:
- model = LocationAreaName
- fields = ("name", "language")
+
+class LocationAreaPokemonEncounterVersionSerializer(serializers.Serializer[Any]):
+ version = VersionSummarySerializer()
+ max_chance = serializers.IntegerField()
+ encounter_details = LocationAreaEncounterDetailSerializer(many=True)
-class LocationAreaDetailSerializer(serializers.ModelSerializer):
+class LocationAreaPokemonEncounterSerializer(serializers.Serializer[Any]):
+ pokemon = PokemonSummarySerializer()
+ version_details = LocationAreaPokemonEncounterVersionSerializer(many=True)
+
+
+class LocationAreaDetailSerializer(serializers.ModelSerializer[LocationArea]):
location = LocationSummarySerializer()
encounter_method_rates = serializers.SerializerMethodField("get_method_rates")
pokemon_encounters = serializers.SerializerMethodField("get_encounters")
@@ -964,260 +1047,72 @@ class Meta:
"pokemon_encounters",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["encounter_method", "version_details"],
- "properties": {
- "encounter_method": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["old-rod"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/encounter-method/2/"],
- },
- },
- },
- "version_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["rate", "version"],
- "properties": {
- "rate": {
- "type": "integer",
- "format": "int32",
- "examples": [5],
- },
- "version": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["platinum"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version/14/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_method_rates(self, obj):
+ @extend_schema_field(LocationAreaEncounterRateSerializer(many=True))
+ def get_method_rates(self, obj: LocationAreaEncounterRate) -> ReturnList[ReturnDict[str, Any]]:
# Get encounters related to this area and pull out unique encounter methods
- encounter_rates = LocationAreaEncounterRate.objects.filter(location_area=obj).order_by("encounter_method_id")
- method_ids = encounter_rates.values("encounter_method_id").distinct()
- encounter_rate_list = []
-
- for id in method_ids:
- encounter_rate_details = OrderedDict()
-
- # Get each Unique Item by ID
- encounter_method_object = EncounterMethod.objects.get(pk=id["encounter_method_id"])
- encounter_method_data = EncounterMethodSummarySerializer(encounter_method_object, context=self.context).data
- encounter_rate_details["encounter_method"] = encounter_method_data
-
- # Get Versions associated with each unique item
- area_encounter_objects = encounter_rates.filter(encounter_method_id=id["encounter_method_id"])
- serializer = LocationAreaEncounterRateSerializer(area_encounter_objects, many=True, context=self.context)
- encounter_rate_details["version_details"] = []
-
- for area_encounter in serializer.data:
- version_detail = OrderedDict()
-
- version_detail["rate"] = area_encounter["rate"]
- version_detail["version"] = area_encounter["version"]
-
- encounter_rate_details["version_details"].append(version_detail)
-
- encounter_rate_list.append(encounter_rate_details)
-
- return encounter_rate_list
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["pokemon", "version_details"],
- "properties": {
- "pokemon": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["tentacool"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon/72/"],
- },
- },
- },
- "version_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["version", "max_chance", "encounter_details"],
- "properties": {
- "version": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["diamond"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version/12/"],
- },
- },
- },
- "max_chance": {
- "type": "integer",
- "format": "int32",
- "examples": [60],
- },
- "encounter_details": {
- "type": "object",
- "required": [
- "min_level",
- "max_level",
- "condition_value",
- "chance",
- "method",
- ],
- "properties": {
- "min_level": {
- "type": "integer",
- "format": "int32",
- "examples": [20],
- },
- "max_level": {
- "type": "integer",
- "format": "int32",
- "examples": [30],
- },
- "condition_values": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["slot2-sapphire"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": [
- "https://pokeapi.co/api/v2/encounter-condition-value/10/"
- ],
- },
- },
- },
- "chance": {
- "type": "integer",
- "format": "int32",
- "examples": [60],
- },
- "method": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["surf"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/encounter-method/5/"],
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_encounters(self, obj):
- # get versions for later use
- version_objects = Version.objects.all().order_by("id")
- version_data = {
- version_object.id: data
- for version_object, data in zip(
- version_objects,
- VersionSummarySerializer(version_objects, many=True, context=self.context).data,
- )
- }
-
- # all encounters associated with location area
- all_encounters = Encounter.objects.filter(location_area=obj).order_by("pokemon")
- encounters_list = []
-
- # break encounters into pokemon groupings
- for poke in all_encounters.values("pokemon").distinct():
- pokemon_object = Pokemon.objects.get(pk=poke["pokemon"])
-
- pokemon_detail = OrderedDict()
- pokemon_detail["pokemon"] = PokemonSummarySerializer(pokemon_object, context=self.context).data
- pokemon_detail["version_details"] = []
-
- poke_encounters = all_encounters.filter(pokemon=poke["pokemon"]).order_by("version")
-
- # each pokemon has multiple versions it could be encountered in
- for ver in poke_encounters.values("version").distinct():
- version_detail = OrderedDict()
- version_detail["version"] = version_data[ver["version"]]
- version_detail["max_chance"] = 0
- version_detail["encounter_details"] = []
-
- poke_data = EncounterDetailSerializer(
- poke_encounters.filter(version=ver["version"]),
- many=True,
- context=self.context,
- ).data
+ rates = (
+ LocationAreaEncounterRate.objects.filter(location_area=obj, encounter_method__isnull=False)
+ .select_related("encounter_method", "version")
+ .order_by("encounter_method_id")
+ )
+ grouped_rates: list[dict[str, Any]] = [
+ {
+ "encounter_method": method,
+ "version_details": list(group_rates),
+ }
+ for method, group_rates in itertools.groupby(rates, key=lambda r: r.encounter_method)
+ ]
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ LocationAreaEncounterRateSerializer(grouped_rates, many=True, context=self.context).data,
+ )
- # each version has multiple ways a pokemon can be encountered
- for encounter in poke_data:
- slot = EncounterSlot.objects.get(pk=encounter["encounter_slot"])
- slot_data = EncounterSlotSerializer(slot, context=self.context).data
- del encounter["pokemon"]
- del encounter["encounter_slot"]
- del encounter["location_area"]
- del encounter["version"]
- encounter["chance"] = slot_data["chance"]
- version_detail["max_chance"] += slot_data["chance"]
- encounter["method"] = slot_data["encounter_method"]
+ @extend_schema_field(LocationAreaPokemonEncounterSerializer(many=True))
+ def get_encounters(self, obj: LocationArea) -> ReturnList[ReturnDict[str, Any]]:
+ encounters = (
+ Encounter.objects.filter(location_area=obj)
+ .select_related(
+ "pokemon",
+ "version",
+ "encounter_slot",
+ "encounter_slot__encounter_method",
+ )
+ .prefetch_related(
+ "encounterconditionvaluemap_set",
+ "encounterpokemondetail_set",
+ )
+ .order_by("pokemon_id", "version_id")
+ )
- version_detail["encounter_details"].append(encounter)
+ grouped_data: list[dict[str, Any]] = []
+ for pokemon, poke_group in itertools.groupby(encounters, key=lambda e: e.pokemon):
+ version_details = []
- pokemon_detail["version_details"].append(version_detail)
+ for version, ver_group in itertools.groupby(poke_group, key=lambda e: e.version):
+ encounter_list = list(ver_group)
+ max_chance = sum(e.encounter_slot.rarity for e in encounter_list if e.encounter_slot)
+ version_details.append(
+ {
+ "version": version,
+ "max_chance": max_chance,
+ "encounter_details": encounter_list,
+ }
+ )
- encounters_list.append(pokemon_detail)
+ grouped_data.append(
+ {
+ "pokemon": pokemon,
+ "version_details": version_details,
+ }
+ )
- return encounters_list
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ LocationAreaPokemonEncounterSerializer(grouped_data, many=True, context=self.context).data,
+ )
-class LocationGameIndexSerializer(serializers.ModelSerializer):
+class LocationGameIndexSerializer(serializers.ModelSerializer[LocationGameIndex]):
generation = GenerationSummarySerializer()
class Meta:
@@ -1225,7 +1120,7 @@ class Meta:
fields = ("game_index", "generation")
-class LocationNameSerializer(serializers.ModelSerializer):
+class LocationNameSerializer(serializers.ModelSerializer[LocationName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1233,7 +1128,7 @@ class Meta:
fields = ("name", "language")
-class LocationDetailSerializer(serializers.ModelSerializer):
+class LocationDetailSerializer(serializers.ModelSerializer[Location]):
region = RegionSummarySerializer()
names = LocationNameSerializer(many=True, read_only=True, source="locationname")
game_indices = LocationGameIndexSerializer(many=True, read_only=True, source="locationgameindex")
@@ -1249,7 +1144,7 @@ class Meta:
#########################
-class AbilityEffectTextSerializer(serializers.ModelSerializer):
+class AbilityEffectTextSerializer(serializers.ModelSerializer[AbilityEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -1257,7 +1152,7 @@ class Meta:
fields = ("effect", "short_effect", "language")
-class AbilityFlavorTextSerializer(serializers.ModelSerializer):
+class AbilityFlavorTextSerializer(serializers.ModelSerializer[AbilityFlavorText]):
flavor_text = serializers.CharField()
language = LanguageSummarySerializer()
version_group = VersionGroupSummarySerializer()
@@ -1267,7 +1162,7 @@ class Meta:
fields = ("flavor_text", "language", "version_group")
-class AbilityChangeEffectTextSerializer(serializers.ModelSerializer):
+class AbilityChangeEffectTextSerializer(serializers.ModelSerializer[AbilityChangeEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -1278,7 +1173,7 @@ class Meta:
)
-class AbilityChangeSerializer(serializers.ModelSerializer):
+class AbilityChangeSerializer(serializers.ModelSerializer[AbilityChange]):
version_group = VersionGroupSummarySerializer()
effect_entries = AbilityChangeEffectTextSerializer(many=True, read_only=True, source="abilitychangeeffecttext")
@@ -1287,7 +1182,7 @@ class Meta:
fields = ("version_group", "effect_entries")
-class AbilityNameSerializer(serializers.ModelSerializer):
+class AbilityNameSerializer(serializers.ModelSerializer[AbilityName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1295,7 +1190,15 @@ class Meta:
fields = ("name", "language")
-class AbilityDetailSerializer(serializers.ModelSerializer):
+class AbilityPokemonDetailSerializer(serializers.ModelSerializer[PokemonAbility]):
+ pokemon = PokemonSummarySerializer()
+
+ class Meta:
+ model = PokemonAbility
+ fields = ("is_hidden", "slot", "pokemon")
+
+
+class AbilityDetailSerializer(serializers.ModelSerializer[Ability]):
effect_entries = AbilityEffectTextSerializer(many=True, read_only=True, source="abilityeffecttext")
flavor_text_entries = AbilityFlavorTextSerializer(many=True, read_only=True, source="abilityflavortext")
names = AbilityNameSerializer(many=True, read_only=True, source="abilityname")
@@ -1317,41 +1220,13 @@ class Meta:
"pokemon",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["is_hidden", "slot", "pokemon"],
- "properties": {
- "is_hidden": {"type": "boolean"},
- "slot": {"type": "integer", "format": "int32", "examples": [3]},
- "pokemon": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["gloom"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon/44/"],
- },
- },
- },
- },
- },
- }
- )
- def get_ability_pokemon(self, obj):
- pokemon_ability_objects = PokemonAbility.objects.filter(ability=obj)
- data = PokemonAbilitySerializer(pokemon_ability_objects, many=True, context=self.context).data
- pokemon = []
-
- for poke in data:
- del poke["ability"]
- pokemon.append(poke)
-
- return pokemon
+ @extend_schema_field(AbilityPokemonDetailSerializer(many=True))
+ def get_ability_pokemon(self, obj: Ability) -> ReturnList[ReturnDict[str, Any]]:
+ pokemon_ability_objects = PokemonAbility.objects.filter(ability=obj).select_related("pokemon")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ AbilityPokemonDetailSerializer(pokemon_ability_objects, many=True, context=self.context).data,
+ )
######################
@@ -1359,7 +1234,7 @@ def get_ability_pokemon(self, obj):
######################
-class StatNameSerializer(serializers.ModelSerializer):
+class StatNameSerializer(serializers.ModelSerializer[StatName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1367,7 +1242,25 @@ class Meta:
fields = ("name", "language")
-class StatDetailSerializer(serializers.ModelSerializer):
+class MoveStatChangeSerializer(serializers.ModelSerializer[MoveMetaStatChange]):
+ move = MoveSummarySerializer()
+
+ class Meta:
+ model = MoveMetaStatChange
+ fields = ("change", "move")
+
+
+class StatAffectingMovesSerializer(serializers.Serializer[Any]):
+ increase = MoveStatChangeSerializer(many=True)
+ decrease = MoveStatChangeSerializer(many=True)
+
+
+class StatAffectingNaturesSerializer(serializers.Serializer[Any]):
+ increase = NatureSummarySerializer(many=True)
+ decrease = NatureSummarySerializer(many=True)
+
+
+class StatDetailSerializer(serializers.ModelSerializer[Stat]):
names = StatNameSerializer(many=True, read_only=True, source="statname")
move_damage_class = MoveDamageClassSummarySerializer()
characteristics = CharacteristicSummarySerializer(many=True, read_only=True, source="characteristic")
@@ -1390,198 +1283,56 @@ class Meta:
"names",
)
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["decrease", "increase"],
- "properties": {
- "increase": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["change", "move"],
- "properties": {
- "change": {
- "type": "integer",
- "format": "int32",
- "examples": [-1],
- },
- "move": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["swords-dance"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/14/"],
- },
- },
- },
- },
- },
- },
- "decrease": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["change", "move"],
- "properties": {
- "change": {
- "type": "integer",
- "format": "int32",
- "examples": [5],
- },
- "move": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["growl"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/45/"],
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_moves_that_affect(self, obj):
- stat_change_objects = MoveMetaStatChange.objects.filter(stat=obj)
- stat_changes = MoveMetaStatChangeSerializer(stat_change_objects, many=True, context=self.context).data
- changes = OrderedDict([("increase", []), ("decrease", [])])
-
- for change in stat_changes:
- del change["stat"]
- if change["change"] > 0:
- changes["increase"].append(change)
- else:
- changes["decrease"].append(change)
-
- return changes
-
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["increase", "decrease"],
- "properties": {
- "increase": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["lonely"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/nature/6/"],
- },
- },
- },
- },
- "decrease": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bold"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/nature/2/"],
- },
- },
- },
- },
- },
- }
- )
- def get_natures_that_affect(self, obj):
- increase_objects = Nature.objects.filter(increased_stat=obj)
- increases = NatureSummarySerializer(increase_objects, many=True, context=self.context).data
- decrease_objects = Nature.objects.filter(decreased_stat=obj)
- decreases = NatureSummarySerializer(decrease_objects, many=True, context=self.context).data
-
- return OrderedDict([("increase", increases), ("decrease", decreases)])
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["protein", "x-attack"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/item/46/"],
- },
- },
- },
- }
- )
- def get_items_that_affect(self, obj):
+ @extend_schema_field(StatAffectingMovesSerializer)
+ def get_moves_that_affect(self, obj: Stat) -> ReturnDict[str, Any]:
+ stat_change_objects = MoveMetaStatChange.objects.filter(stat=obj).select_related("move")
+ increases = stat_change_objects.filter(change__gt=0)
+ decreases = stat_change_objects.filter(change__lte=0)
+
+ return cast(
+ "ReturnDict[str, Any]",
+ StatAffectingMovesSerializer({"increase": increases, "decrease": decreases}, context=self.context).data,
+ )
+
+ @extend_schema_field(StatAffectingNaturesSerializer)
+ def get_natures_that_affect(self, obj: Stat) -> ReturnDict[str, Any]:
+ increases = Nature.objects.filter(increased_stat=obj)
+ decreases = Nature.objects.filter(decreased_stat=obj)
+
+ return cast(
+ "ReturnDict[str, Any]",
+ StatAffectingNaturesSerializer({"increase": increases, "decrease": decreases}, context=self.context).data,
+ )
+
+ @extend_schema_field(ItemSummarySerializer(many=True))
+ def get_items_that_affect(self, obj: Stat) -> ReturnList[ReturnDict[str, Any]]:
"""
Get items that affect this stat (like vitamins, X-items, etc.)
"""
- # Map stat names to their corresponding vitamin items
+ # Map stat names to their corresponding vitamin and X-item names
stat_item_mapping = {
"hp": ["hp-up"],
- "attack": ["protein"],
- "defense": ["iron"],
- "special-attack": ["calcium"],
- "special-defense": ["zinc"],
- "speed": ["carbos"],
+ "attack": ["protein", "x-attack"],
+ "defense": ["iron", "x-defense"],
+ "special-attack": ["calcium", "x-sp-atk"],
+ "special-defense": ["zinc", "x-sp-def"],
+ "speed": ["carbos", "x-speed"],
+ "accuracy": ["x-accuracy"],
+ "evasion": ["x-evasion"],
}
# Get the stat name (lowercase)
stat_name = obj.name.lower()
+ # Get the corresponding item names for this stat
+ item_names = stat_item_mapping.get(stat_name, [])
- # Find items that affect this stat
- affecting_items = []
-
- # Check for vitamin items
- if stat_name in stat_item_mapping:
- for item_identifier in stat_item_mapping[stat_name]:
- try:
- item = Item.objects.get(name=item_identifier)
- affecting_items.append(ItemSummarySerializer(item, context=self.context).data)
- except Item.DoesNotExist:
- pass
-
- # Check for X-items (like X Attack, X Defense, etc.)
- x_item_mapping = {
- "attack": ["x-attack"],
- "defense": ["x-defense"],
- "special-attack": ["x-sp-atk"],
- "special-defense": ["x-sp-def"],
- "speed": ["x-speed"],
- "accuracy": ["x-accuracy"],
- "evasion": ["x-evasion"],
- }
-
- if stat_name in x_item_mapping:
- for item_identifier in x_item_mapping[stat_name]:
- try:
- item = Item.objects.get(name=item_identifier)
- affecting_items.append(ItemSummarySerializer(item, context=self.context).data)
- except Item.DoesNotExist:
- pass
+ if not item_names:
+ return cast("ReturnList[ReturnDict[str, Any]]", [])
- return affecting_items
+ items = Item.objects.filter(name__in=item_names)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]", ItemSummarySerializer(items, many=True, context=self.context).data
+ )
#############################
@@ -1589,15 +1340,15 @@ def get_items_that_affect(self, obj):
#############################
-class ItemPocketNameSerializer(serializers.ModelSerializer):
+class ItemPocketNameSerializer(serializers.ModelSerializer[ItemPocketName]):
language = LanguageSummarySerializer()
class Meta:
- model = ItemName
+ model = ItemPocketName
fields = ("name", "language")
-class ItemPocketDetailSerializer(serializers.ModelSerializer):
+class ItemPocketDetailSerializer(serializers.ModelSerializer[ItemPocket]):
names = ItemPocketNameSerializer(many=True, read_only=True, source="itempocketname")
categories = ItemCategorySummarySerializer(many=True, read_only=True, source="itemcategory")
@@ -1609,15 +1360,17 @@ class Meta:
###############################
# ITEM CATEGORY SERIALIZERS #
###############################
-class ItemCategoryNameSerializer(serializers.ModelSerializer):
+
+
+class ItemCategoryNameSerializer(serializers.ModelSerializer[ItemCategoryName]):
language = LanguageSummarySerializer()
class Meta:
- model = ItemName
+ model = ItemCategoryName
fields = ("name", "language")
-class ItemCategoryDetailSerializer(serializers.ModelSerializer):
+class ItemCategoryDetailSerializer(serializers.ModelSerializer[ItemCategory]):
names = ItemCategoryNameSerializer(many=True, read_only=True, source="itemcategoryname")
pocket = ItemPocketSummarySerializer(source="item_pocket")
items = ItemSummarySerializer(many=True, read_only=True, source="item")
@@ -1632,7 +1385,7 @@ class Meta:
################################
-class ItemAttributeNameSerializer(serializers.ModelSerializer):
+class ItemAttributeNameSerializer(serializers.ModelSerializer[ItemAttributeName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1640,7 +1393,7 @@ class Meta:
fields = ("name", "language")
-class ItemAttributeDescriptionSerializer(serializers.ModelSerializer):
+class ItemAttributeDescriptionSerializer(serializers.ModelSerializer[ItemAttributeDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -1648,7 +1401,7 @@ class Meta:
fields = ("description", "language")
-class ItemAttributeDetailSerializer(serializers.ModelSerializer):
+class ItemAttributeDetailSerializer(serializers.ModelSerializer[ItemAttribute]):
names = ItemAttributeNameSerializer(many=True, read_only=True, source="itemattributename")
descriptions = ItemAttributeDescriptionSerializer(many=True, read_only=True, source="itemattributedescription")
items = serializers.SerializerMethodField("get_attribute_items")
@@ -1657,33 +1410,12 @@ class Meta:
model = ItemAttribute
fields = ("id", "name", "descriptions", "items", "names")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["master-ball"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/item/1/"],
- },
- },
- },
- }
- )
- def get_attribute_items(self, obj):
- item_map_objects = ItemAttributeMap.objects.filter(item_attribute=obj)
- items = []
-
- for map in item_map_objects:
- item_obj = Item.objects.get(pk=map.item.id)
- item = ItemSummarySerializer(item_obj, context=self.context).data
- items.append(item)
-
- return items
+ @extend_schema_field(ItemSummarySerializer(many=True))
+ def get_attribute_items(self, obj: ItemAttribute) -> ReturnList[ReturnDict[str, Any]]:
+ items = Item.objects.filter(itemattributemap__item_attribute=obj, itemattributemap__item__isnull=False)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]", ItemSummarySerializer(items, many=True, context=self.context).data
+ )
###########################
@@ -1691,7 +1423,7 @@ def get_attribute_items(self, obj):
###########################
-class CurrencyNameSerializer(serializers.ModelSerializer):
+class CurrencyNameSerializer(serializers.ModelSerializer[CurrencyName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1699,7 +1431,7 @@ class Meta:
fields = ("name", "language")
-class CurrencyDetailSerializer(serializers.ModelSerializer):
+class CurrencyDetailSerializer(serializers.ModelSerializer[Currency]):
names = CurrencyNameSerializer(many=True, read_only=True, source="currencyname")
class Meta:
@@ -1710,7 +1442,9 @@ class Meta:
###################################
# ITEM FLING EFFECT SERIALIZERS #
###################################
-class ItemFlingEffectEffectTextSerializer(serializers.ModelSerializer):
+
+
+class ItemFlingEffectEffectTextSerializer(serializers.ModelSerializer[ItemFlingEffectEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -1718,7 +1452,7 @@ class Meta:
fields = ("effect", "language")
-class ItemFlingEffectDetailSerializer(serializers.ModelSerializer):
+class ItemFlingEffectDetailSerializer(serializers.ModelSerializer[ItemFlingEffect]):
effect_entries = ItemFlingEffectEffectTextSerializer(many=True, read_only=True, source="itemflingeffecteffecttext")
items = ItemSummarySerializer(many=True, read_only=True, source="item")
@@ -1730,7 +1464,9 @@ class Meta:
#######################
# ITEM SERIALIZERS #
#######################
-class ItemFlavorTextSerializer(serializers.ModelSerializer):
+
+
+class ItemFlavorTextSerializer(serializers.ModelSerializer[ItemFlavorText]):
text = serializers.CharField(source="flavor_text")
language = LanguageSummarySerializer()
version_group = VersionGroupSummarySerializer()
@@ -1740,7 +1476,7 @@ class Meta:
fields = ("text", "version_group", "language")
-class ItemEffectTextSerializer(serializers.ModelSerializer):
+class ItemEffectTextSerializer(serializers.ModelSerializer[ItemEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -1748,7 +1484,7 @@ class Meta:
fields = ("effect", "short_effect", "language")
-class ItemGameIndexSerializer(serializers.ModelSerializer):
+class ItemGameIndexSerializer(serializers.ModelSerializer[ItemGameIndex]):
generation = GenerationSummarySerializer()
class Meta:
@@ -1756,7 +1492,7 @@ class Meta:
fields = ("game_index", "generation")
-class ItemPriceSerializer(serializers.ModelSerializer):
+class ItemPriceSerializer(serializers.ModelSerializer[ItemPrice]):
currency = CurrencySummarySerializer()
version_group = VersionGroupSummarySerializer()
@@ -1770,7 +1506,7 @@ class Meta:
)
-class ItemNameSerializer(serializers.ModelSerializer):
+class ItemNameSerializer(serializers.ModelSerializer[ItemName]):
language = LanguageSummarySerializer()
class Meta:
@@ -1778,13 +1514,30 @@ class Meta:
fields = ("name", "language")
-class ItemSpritesSerializer(serializers.ModelSerializer):
+class ItemSpritesSerializer(serializers.Serializer[Any]):
+ default = serializers.CharField(allow_null=True)
+
+
+class PokemonHeldItemVersionSerializer(serializers.Serializer[Any]):
+ rarity = serializers.IntegerField()
+ version = VersionSummarySerializer()
+
+
+class PokemonHeldItemSerializer(serializers.Serializer[Any]):
+ pokemon = PokemonSummarySerializer()
+ version_details = PokemonHeldItemVersionSerializer(many=True)
+
+
+class ItemMachineSerializer(serializers.ModelSerializer[Machine]):
+ machine = MachineSummarySerializer(source="*")
+ version_group = VersionGroupSummarySerializer()
+
class Meta:
- model = ItemSprites
- fields = ("sprites",)
+ model = Machine
+ fields = ("machine", "version_group")
-class ItemDetailSerializer(serializers.ModelSerializer):
+class ItemDetailSerializer(serializers.ModelSerializer[Item]):
names = ItemNameSerializer(many=True, read_only=True, source="itemname")
game_indices = ItemGameIndexSerializer(many=True, read_only=True, source="itemgameindex")
prices = ItemPriceSerializer(many=True, read_only=True, source="itemprice")
@@ -1793,8 +1546,8 @@ class ItemDetailSerializer(serializers.ModelSerializer):
category = ItemCategorySummarySerializer(source="item_category")
attributes = serializers.SerializerMethodField("get_item_attributes")
fling_effect = ItemFlingEffectSummarySerializer(source="item_fling_effect")
- held_by_pokemon = serializers.SerializerMethodField(source="get_held_by_pokemon")
- baby_trigger_for = serializers.SerializerMethodField(source="get_baby_trigger_for")
+ held_by_pokemon = serializers.SerializerMethodField("get_held_by_pokemon")
+ baby_trigger_for = serializers.SerializerMethodField("get_baby_trigger_for")
sprites = serializers.SerializerMethodField("get_item_sprites")
machines = serializers.SerializerMethodField("get_item_machines")
@@ -1818,206 +1571,61 @@ class Meta:
"machines",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["machine", "version_group"],
- "properties": {
- "machine": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/machine/1/"],
- },
- "version_group": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["sword-shield"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/20/"],
- },
- },
- },
- },
- },
- }
- )
- def get_item_machines(self, obj):
- machine_objects = Machine.objects.filter(item=obj)
+ @extend_schema_field(ItemMachineSerializer(many=True))
+ def get_item_machines(self, obj: Item) -> list[ReturnDict[str, Any]]:
+ machine_objects = Machine.objects.filter(item=obj).select_related("version_group")
+ return cast(
+ "list[ReturnDict[str, Any]]",
+ ItemMachineSerializer(machine_objects, many=True, context=self.context).data,
+ )
- machines = []
+ @extend_schema_field(ItemSpritesSerializer)
+ def get_item_sprites(self, obj: Item) -> dict[str, str | None]:
+ sprites_object = ItemSprites.objects.filter(item=obj).first()
+ return sprites_object.sprites if sprites_object else {}
+
+ @extend_schema_field(ItemAttributeSummarySerializer(many=True))
+ def get_item_attributes(self, obj: Item) -> ReturnList[ReturnDict[str, Any]]:
+ attributes = ItemAttribute.objects.filter(itemattributemap__item=obj)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ ItemAttributeSummarySerializer(attributes, many=True, context=self.context).data,
+ )
- for machine_object in machine_objects:
- machine_data = MachineSummarySerializer(machine_object, context=self.context).data
+ @extend_schema_field(PokemonHeldItemSerializer(many=True))
+ def get_held_by_pokemon(self, obj: Item) -> ReturnList[ReturnDict[str, Any]]:
+ pokemon_items = (
+ PokemonItem.objects.filter(item=obj)
+ .select_related("pokemon", "version")
+ .order_by("pokemon_id", "version_id")
+ )
+ grouped_data: list[dict[str, Any]] = [
+ {
+ "pokemon": pokemon,
+ "version_details": list(items),
+ }
+ for pokemon, items in itertools.groupby(pokemon_items, key=lambda pi: pi.pokemon)
+ ]
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonHeldItemSerializer(grouped_data, many=True, context=self.context).data,
+ )
- version_group_data = VersionGroupSummarySerializer(machine_object.version_group, context=self.context).data
+ @extend_schema_field(EvolutionChainSummarySerializer(allow_null=True))
+ def get_baby_trigger_for(self, obj: Item) -> ReturnDict[str, Any] | None:
+ chain_object = EvolutionChain.objects.filter(baby_trigger_item=obj).first()
+ return cast(
+ "ReturnDict[str, Any] | None",
+ EvolutionChainSummarySerializer(chain_object, context=self.context).data if chain_object else None,
+ )
- machines.append({"machine": machine_data, "version_group": version_group_data})
- return machines
+########################
+# NATURE SERIALIZERS #
+########################
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["default"],
- "properties": {
- "default": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/media/sprites/items/master-ball.png"],
- }
- },
- }
- )
- def get_item_sprites(self, obj):
- sprites_object = ItemSprites.objects.get(item_id=obj)
- return sprites_object.sprites
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["countable"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/item-attribute/1/"],
- },
- },
- },
- }
- )
- def get_item_attributes(self, obj):
- item_attribute_maps = ItemAttributeMap.objects.filter(item=obj)
- serializer = ItemAttributeMapSerializer(item_attribute_maps, many=True, context=self.context)
- data = serializer.data
-
- attributes = []
-
- for map in data:
- attribute = OrderedDict()
- attribute["name"] = map["attribute"]["name"]
- attribute["url"] = map["attribute"]["url"]
- attributes.append(attribute)
-
- return attributes
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["pokemon", "version-details"],
- "properties": {
- "pokemon": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["farfetchd"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon/83/"],
- },
- },
- },
- "version-details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["rarity", "version"],
- "properties": {
- "rarity": {
- "type": "integer",
- "format": "int32",
- "examples": [5],
- },
- "version": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["ruby"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version/7/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_held_by_pokemon(self, obj):
- pokemon_items = PokemonItem.objects.filter(item=obj).order_by("pokemon_id")
- pokemon_ids = pokemon_items.values("pokemon_id").distinct()
- pokemon_list = []
-
- for id in pokemon_ids:
- item_pokemon_details = OrderedDict()
-
- # Get each Unique Item by ID
- pokemon_object = Pokemon.objects.get(pk=id["pokemon_id"])
- pokemon_data = PokemonSummarySerializer(pokemon_object, context=self.context).data
- item_pokemon_details["pokemon"] = pokemon_data
-
- # Get Versions associated with each unique item
- pokemon_item_objects = pokemon_items.filter(pokemon_id=id["pokemon_id"])
- serializer = PokemonItemSerializer(pokemon_item_objects, many=True, context=self.context)
- item_pokemon_details["version_details"] = []
-
- for pokemon in serializer.data:
- version_detail = OrderedDict()
- version_detail["rarity"] = pokemon["rarity"]
- version_detail["version"] = pokemon["version"]
- item_pokemon_details["version_details"].append(version_detail)
-
- pokemon_list.append(item_pokemon_details)
-
- return pokemon_list
-
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["url"],
- "properties": {
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/evolution-chain/51/"],
- }
- },
- }
- )
- def get_baby_trigger_for(self, obj):
- try:
- chain_object = EvolutionChain.objects.get(baby_trigger_item=obj)
- data = EvolutionChainSummarySerializer(chain_object, context=self.context).data
- except EvolutionChain.DoesNotExist:
- data = None
-
- return data
-
-
-########################
-# NATURE SERIALIZERS #
-########################
-
-class NatureBattleStylePreferenceSerializer(serializers.ModelSerializer):
+class NatureBattleStylePreferenceSerializer(serializers.ModelSerializer[NatureBattleStylePreference]):
move_battle_style = MoveBattleStyleSummarySerializer()
class Meta:
@@ -2029,7 +1637,7 @@ class Meta:
)
-class NatureNameSerializer(serializers.ModelSerializer):
+class NatureNameSerializer(serializers.ModelSerializer[NatureName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2037,7 +1645,15 @@ class Meta:
fields = ("name", "language")
-class NatureDetailSerializer(serializers.ModelSerializer):
+class NaturePokeathlonStatSerializer(serializers.ModelSerializer[NaturePokeathlonStat]):
+ pokeathlon_stat = PokeathlonStatSummarySerializer()
+
+ class Meta:
+ model = NaturePokeathlonStat
+ fields = ("max_change", "pokeathlon_stat")
+
+
+class NatureDetailSerializer(serializers.ModelSerializer[Nature]):
names = NatureNameSerializer(many=True, read_only=True, source="naturename")
decreased_stat = StatSummarySerializer()
increased_stat = StatSummarySerializer()
@@ -2064,42 +1680,13 @@ class Meta:
"names",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["max_change", "pokeathlon_stat"],
- "properties": {
- "max_change": {
- "type": "integer",
- "format": "int32",
- "examples": [1],
- },
- "pokeathlon_stat": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["power"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokeathlon-stat/2/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokeathlon_stats(self, obj):
- pokeathlon_stat_objects = NaturePokeathlonStat.objects.filter(nature=obj)
- pokeathlon_stats = NaturePokeathlonStatSerializer(pokeathlon_stat_objects, many=True, context=self.context).data
-
- for stat in pokeathlon_stats:
- del stat["nature"]
-
- return pokeathlon_stats
+ @extend_schema_field(NaturePokeathlonStatSerializer(many=True))
+ def get_pokeathlon_stats(self, obj: Nature) -> ReturnList[ReturnDict[str, Any]]:
+ pokeathlon_stat_objects = NaturePokeathlonStat.objects.filter(nature=obj).select_related("pokeathlon_stat")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ NaturePokeathlonStatSerializer(pokeathlon_stat_objects, many=True, context=self.context).data,
+ )
#######################
@@ -2107,7 +1694,7 @@ def get_pokeathlon_stats(self, obj):
#######################
-class BerryFirmnessNameSerializer(serializers.ModelSerializer):
+class BerryFirmnessNameSerializer(serializers.ModelSerializer[BerryFirmnessName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2115,7 +1702,7 @@ class Meta:
fields = ("name", "language")
-class BerryFirmnessDetailSerializer(serializers.ModelSerializer):
+class BerryFirmnessDetailSerializer(serializers.ModelSerializer[BerryFirmness]):
names = BerryFirmnessNameSerializer(many=True, read_only=True, source="berryfirmnessname")
berries = BerrySummarySerializer(many=True, read_only=True, source="berry")
@@ -2124,7 +1711,7 @@ class Meta:
fields = ("id", "name", "berries", "names")
-class BerryFlavorNameSerializer(serializers.ModelSerializer):
+class BerryFlavorNameSerializer(serializers.ModelSerializer[BerryFlavorName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2132,7 +1719,15 @@ class Meta:
fields = ("name", "language")
-class BerryFlavorDetailSerializer(serializers.ModelSerializer):
+class BerryFlavorBerryMapSerializer(serializers.ModelSerializer[BerryFlavorMap]):
+ berry = BerrySummarySerializer()
+
+ class Meta:
+ model = BerryFlavorMap
+ fields = ("potency", "berry")
+
+
+class BerryFlavorDetailSerializer(serializers.ModelSerializer[BerryFlavor]):
names = BerryFlavorNameSerializer(many=True, read_only=True, source="berryflavorname")
contest_type = ContestTypeSummarySerializer()
berries = serializers.SerializerMethodField("get_berries_with_flavor")
@@ -2141,46 +1736,26 @@ class Meta:
model = BerryFlavor
fields = ("id", "name", "berries", "contest_type", "names")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["potency", "berry"],
- "properties": {
- "potency": {"type": "integer", "examples": [10]},
- "berry": {
- "type": "object",
- "require": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the berry",
- "examples": ["rowap"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "description": "The URL to get more information about the berry",
- "examples": ["https://pokeapi.co/api/v2/berry/64/"],
- },
- },
- },
- },
- },
- }
- )
- def get_berries_with_flavor(self, obj):
- flavor_map_objects = BerryFlavorMap.objects.filter(berry_flavor=obj, potency__gt=0).order_by("potency")
- flavor_maps = BerryFlavorMapSerializer(flavor_map_objects, many=True, context=self.context).data
+ @extend_schema_field(BerryFlavorBerryMapSerializer(many=True))
+ def get_berries_with_flavor(self, obj: BerryFlavor) -> ReturnList[ReturnDict[str, Any]]:
+ flavor_map_objects = (
+ BerryFlavorMap.objects.filter(berry_flavor=obj, potency__gt=0).select_related("berry").order_by("potency")
+ )
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ BerryFlavorBerryMapSerializer(flavor_map_objects, many=True, context=self.context).data,
+ )
- for map in flavor_maps:
- del map["flavor"]
- return flavor_maps
+class BerryFlavorMapSerializer(serializers.ModelSerializer[BerryFlavorMap]):
+ flavor = BerryFlavorSummarySerializer(source="berry_flavor")
+
+ class Meta:
+ model = BerryFlavorMap
+ fields = ("potency", "flavor")
-class BerryDetailSerializer(serializers.ModelSerializer):
+class BerryDetailSerializer(serializers.ModelSerializer[Berry]):
item = ItemSummarySerializer()
natural_gift_type = TypeSummarySerializer()
firmness = BerryFirmnessSummarySerializer(source="berry_firmness")
@@ -2203,60 +1778,21 @@ class Meta:
"natural_gift_type",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["potency", "flavor"],
- "properties": {
- "potency": {"type": "integer", "examples": [10]},
- "flavor": {
- "type": "object",
- "require": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the flavor",
- "examples": ["spicy"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "description": "The URL to get more information about the flavor",
- "examples": ["https://pokeapi.co/api/v2/berry-flavor/1/"],
- },
- },
- },
- },
- },
- }
- )
- def get_berry_flavors(self, obj):
- flavor_map_objects = BerryFlavorMap.objects.filter(berry=obj)
- flavor_maps = BerryFlavorMapSerializer(flavor_map_objects, many=True, context=self.context).data
- flavors = []
-
- for map in flavor_maps:
- del map["berry"]
- flavors.append(map)
-
- return flavors
+ @extend_schema_field(BerryFlavorMapSerializer(many=True))
+ def get_berry_flavors(self, obj: Berry) -> ReturnList[ReturnDict[str, Any]]:
+ flavor_map_objects = BerryFlavorMap.objects.filter(berry=obj).select_related("berry_flavor")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ BerryFlavorMapSerializer(flavor_map_objects, many=True, context=self.context).data,
+ )
###########################
# EGG GROUP SERIALIZERS #
###########################
-class PokemonEggGroupSerializer(serializers.ModelSerializer):
- species = PokemonSpeciesSummarySerializer(source="pokemon_species")
- egg_group = EggGroupSummarySerializer()
-
- class Meta:
- model = PokemonEggGroup
- fields = ("species", "egg_group")
-class EggGroupNameSerializer(serializers.ModelSerializer):
+class EggGroupNameSerializer(serializers.ModelSerializer[EggGroupName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2264,7 +1800,7 @@ class Meta:
fields = ("name", "language")
-class EggGroupDetailSerializer(serializers.ModelSerializer):
+class EggGroupDetailSerializer(serializers.ModelSerializer[EggGroup]):
names = EggGroupNameSerializer(many=True, read_only=True, source="egggroupname")
pokemon_species = serializers.SerializerMethodField("get_species")
@@ -2272,49 +1808,21 @@ class Meta:
model = EggGroup
fields = ("id", "name", "names", "pokemon_species")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["potency", "flavor"],
- "properties": {
- "name": {
- "type": "string",
- "description": "Pokemon species name.",
- "examples": ["bulbasaur"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "description": "The URL to get more information about the species",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/1/"],
- },
- },
- },
- }
- )
- def get_species(self, obj):
- results = PokemonEggGroup.objects.filter(egg_group=obj)
- data = PokemonEggGroupSerializer(results, many=True, context=self.context).data
- associated_species = []
- for species in data:
- associated_species.append(species["species"])
-
- return associated_species
+ @extend_schema_field(PokemonSpeciesSummarySerializer(many=True))
+ def get_species(self, obj: EggGroup) -> ReturnList[ReturnDict[str, Any]]:
+ species = PokemonSpecies.objects.filter(pokemonegggroup__egg_group=obj)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesSummarySerializer(species, many=True, context=self.context).data,
+ )
######################
# TYPE SERIALIZERS #
######################
-# https://stackoverflow.com/a/45987450/3482533
-class TypeEfficacySerializer(serializers.ModelSerializer):
- class Meta:
- model = TypeEfficacy
- fields = "__all__"
-class TypeEfficacyPastSerializer(serializers.ModelSerializer):
+class TypeEfficacyPastSerializer(serializers.ModelSerializer[TypeEfficacyPast]):
generation = GenerationSummarySerializer()
class Meta:
@@ -2322,7 +1830,7 @@ class Meta:
fields = ("target_type", "damage_type", "damage_factor", "generation")
-class TypeGameIndexSerializer(serializers.ModelSerializer):
+class TypeGameIndexSerializer(serializers.ModelSerializer[TypeGameIndex]):
generation = GenerationSummarySerializer()
class Meta:
@@ -2330,7 +1838,7 @@ class Meta:
fields = ("game_index", "generation")
-class TypeNameSerializer(serializers.ModelSerializer):
+class TypeNameSerializer(serializers.ModelSerializer[TypeName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2338,19 +1846,41 @@ class Meta:
fields = ("name", "language")
-class TypeSpriteSerializer(serializers.ModelSerializer):
+class TypeSpriteSerializer(serializers.ModelSerializer[TypeSprites]):
class Meta:
model = TypeSprites
fields = ("sprites",)
-class TypeDetailSerializer(serializers.ModelSerializer):
+class TypeRelationshipsSerializer(serializers.Serializer[Any]):
+ no_damage_to = TypeSummarySerializer(many=True)
+ half_damage_to = TypeSummarySerializer(many=True)
+ double_damage_to = TypeSummarySerializer(many=True)
+ no_damage_from = TypeSummarySerializer(many=True)
+ half_damage_from = TypeSummarySerializer(many=True)
+ double_damage_from = TypeSummarySerializer(many=True)
+
+
+class TypePastRelationshipsSerializer(serializers.Serializer[Any]):
+ generation = GenerationSummarySerializer()
+ damage_relations = TypeRelationshipsSerializer()
+
+
+class TypePokemonSerializer(serializers.ModelSerializer[PokemonType]):
+ pokemon = PokemonSummarySerializer()
+
+ class Meta:
+ model = PokemonType
+ fields = ("slot", "pokemon")
+
+
+class TypeDetailSerializer(serializers.ModelSerializer[Type]):
"""
Serializer for the Type resource
"""
generation = GenerationSummarySerializer()
- names = AbilityNameSerializer(many=True, read_only=True, source="typename")
+ names = TypeNameSerializer(many=True, read_only=True, source="typename")
game_indices = TypeGameIndexSerializer(many=True, read_only=True, source="typegameindex")
move_damage_class = MoveDamageClassSummarySerializer()
damage_relations = serializers.SerializerMethodField("get_type_relationships")
@@ -2359,6 +1889,16 @@ class TypeDetailSerializer(serializers.ModelSerializer):
moves = MoveSummarySerializer(many=True, read_only=True, source="move")
sprites = serializers.SerializerMethodField("get_type_sprites")
+ FACTOR_PREFIX_MAP: ClassVar[dict[int, str]] = {200: "double", 50: "half", 0: "no"}
+ RELATION_KEYS = (
+ "no_damage_to",
+ "half_damage_to",
+ "double_damage_to",
+ "no_damage_from",
+ "half_damage_from",
+ "double_damage_from",
+ )
+
class Meta:
model = Type
fields = (
@@ -2375,509 +1915,126 @@ class Meta:
"sprites",
)
- @extend_schema_field(
- field={
- "type": "object",
- "additionalProperties": {
- "type": "object",
- "additionalProperties": {
- "type": "object",
- "properties": {
- "name-icon": {
- "type": "string",
- "format": "uri",
- "examples": [
- "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png"
- ],
- }
- },
- "examples": [
- {
- "colosseum": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png"
- }
- }
- ],
- },
- "examples": [
- {
- "generation-ix": {
- "scarlet-violet": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-ix/scarlet-violet/1.png"
- }
- }
- }
- ],
- },
- "examples": [
- {
- "sprites": {
- "generation-iii": {
- "colosseum": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/colosseum/1.png"
- },
- "emerald": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/emerald/1.png"
- },
- "firered-leafgreen": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/firered-leafgreen/1.png"
- },
- "ruby-sapphire": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/ruby-sapphire/1.png"
- },
- "xd": {
- "name_icon": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-iii/xd/1.png"
- },
- }
- }
- }
- ],
- }
- )
- def get_type_sprites(self, obj):
- sprites_object = TypeSprites.objects.get(type_id=obj)
- return sprites_object.sprites
-
- # adds an entry for the given type with the given damage
- # factor in the given direction to the set of relations
-
- def add_type_entry(self, relations, type, damage_factor, direction="_damage_to"):
- if damage_factor == 200:
- relations["double" + direction].append(TypeSummarySerializer(type, context=self.context).data)
- elif damage_factor == 50:
- relations["half" + direction].append(TypeSummarySerializer(type, context=self.context).data)
- elif damage_factor == 0:
- relations["no" + direction].append(TypeSummarySerializer(type, context=self.context).data)
-
- @extend_schema_field(
- field={
- "type": "object",
- "required": [
- "no_damage_to",
- "half_damage_to",
- "double_damage_to",
- "no_damage_from",
- "half_damage_from",
- "double_damage_from",
- ],
- "properties": {
- "no_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["flying"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/3/"],
- },
- },
- },
- },
- "half_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bug"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/7/"],
- },
- },
- },
- },
- "double_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["poison"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/4/"],
- },
- },
- },
- },
- "no_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["electric"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/13/"],
- },
- },
- },
- },
- "half_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["poison"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/4/"],
- },
- },
- },
- },
- "double_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["water"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/11/"],
- },
- },
- },
- },
- },
- }
- )
- def get_type_relationships(self, obj):
- relations = OrderedDict()
- relations["no_damage_to"] = []
- relations["half_damage_to"] = []
- relations["double_damage_to"] = []
+ @extend_schema_field(TypeSpriteSerializer)
+ def get_type_sprites(self, obj: Type) -> dict[str, str | None]:
+ sprites_object = TypeSprites.objects.filter(type=obj).first()
+ return sprites_object.sprites if sprites_object else {}
- relations["no_damage_from"] = []
- relations["half_damage_from"] = []
- relations["double_damage_from"] = []
+ def add_type_entry(
+ self, relations: dict[str, list[Any]], type_obj: Type, damage_factor: int, direction: str = "_damage_to"
+ ) -> None:
+ """
+ Add an entry for the given type with the given damage factor in the given direction to the set of relations.
+ """
+ if prefix := self.FACTOR_PREFIX_MAP.get(damage_factor):
+ type_data = cast("ReturnDict[str, Any]", TypeSummarySerializer(type_obj, context=self.context).data)
+ relations[f"{prefix}{direction}"].append(type_data)
- # Damage To
- results = TypeEfficacy.objects.filter(damage_type=obj)
- serializer = TypeEfficacySerializer(results, many=True, context=self.context)
+ @extend_schema_field(TypeRelationshipsSerializer)
+ def get_type_relationships(self, obj: Type) -> dict[str, list[dict[str, Any]]]:
+ relations: dict[str, list[dict[str, Any]]] = {key: [] for key in self.RELATION_KEYS}
- for relation in serializer.data:
- type = Type.objects.get(pk=relation["target_type"])
- damage_factor = relation["damage_factor"]
- self.add_type_entry(relations, type, damage_factor, direction="_damage_to")
+ # Damage To
+ damage_to_efficacy = TypeEfficacy.objects.filter(damage_type=obj).select_related("target_type")
+ for efficacy in damage_to_efficacy:
+ if efficacy.target_type:
+ self.add_type_entry(relations, efficacy.target_type, efficacy.damage_factor, direction="_damage_to")
# Damage From
- results = TypeEfficacy.objects.filter(target_type=obj)
- serializer = TypeEfficacySerializer(results, many=True, context=self.context)
-
- for relation in serializer.data:
- type = Type.objects.get(pk=relation["damage_type"])
- damage_factor = relation["damage_factor"]
- self.add_type_entry(relations, type, damage_factor, direction="_damage_from")
+ damage_from_efficacy = TypeEfficacy.objects.filter(target_type=obj).select_related("damage_type")
+ for efficacy in damage_from_efficacy:
+ if efficacy.damage_type:
+ self.add_type_entry(relations, efficacy.damage_type, efficacy.damage_factor, direction="_damage_from")
return relations
- # takes a list of past type relations by generation and
- # returns a list of lists where each list has the entries
- # for a single generation
- def group_relations_by_generation(self, serializer_data):
- data_by_gen = []
-
- current_generation = ""
- generation_data = []
- for relation in serializer_data:
- gen_name = relation["generation"]["name"]
- if gen_name != current_generation:
- # first item for this generation so create its list
- current_generation = gen_name
- generation_data = [relation]
- data_by_gen.append(generation_data)
- else:
- # add to this generation's list
- generation_data.append(relation)
-
- return data_by_gen
-
- # removes the entry for the given type in
- # the given direction from the set of relations
- def remove_type_entry(self, relations, type, direction="_damage_to"):
- for k in ["double", "half", "no"]:
- rel_list = relations[k + direction]
- for i, o in enumerate(rel_list):
- if o["name"] == type.name:
+ def remove_type_entry(self, relations: dict[str, list[Any]], type_obj: Type, direction: str = "_damage_to") -> None:
+ """
+ Remove the entry for the given type in the given direction from the set of relations.
+ """
+ for prefix in ("double", "half", "no"):
+ rel_list = relations[f"{prefix}{direction}"]
+ for i, item in enumerate(rel_list):
+ if item["name"] == type_obj.name:
del rel_list[i]
return
- # returns past type relationships for the given type object
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["generation", "damage_relations"],
- "properties": {
- "generation": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["generation-v"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/generation/5/"],
- },
- },
- },
- "damage_relations": {
- "type": "object",
- "required": [
- "no_damage_to",
- "half_damage_to",
- "double_damage_to",
- "no_damage_from",
- "half_damage_from",
- "double_damage_from",
- ],
- "properties": {
- "no_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["flying"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/3/"],
- },
- },
- },
- },
- "half_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bug"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/7/"],
- },
- },
- },
- },
- "double_damage_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["poison"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/4/"],
- },
- },
- },
- },
- "no_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["electric"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/13/"],
- },
- },
- },
- },
- "half_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["poison"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/4/"],
- },
- },
- },
- },
- "double_damage_from": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["water"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/11/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_type_past_relationships(self, obj):
+ @extend_schema_field(TypePastRelationshipsSerializer(many=True))
+ def get_type_past_relationships(self, obj: Type) -> list[dict[str, Any]]:
+ """Returns a list of past type relationships for the given type object, grouped by generation."""
# collect data from DB
- damage_type_results = list(TypeEfficacyPast.objects.filter(damage_type=obj))
- target_type_results = list(TypeEfficacyPast.objects.filter(target_type=obj))
- serializer = TypeEfficacyPastSerializer(
- damage_type_results + target_type_results, many=True, context=self.context
+ all_past_efficacy = list(
+ TypeEfficacyPast.objects.filter(Q(damage_type=obj) | Q(target_type=obj)).select_related(
+ "generation", "target_type", "damage_type"
+ )
+ )
+ if not all_past_efficacy:
+ return []
+
+ serializer_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ TypeEfficacyPastSerializer(all_past_efficacy, many=True, context=self.context).data,
)
# group data by generation
- data_by_gen = self.group_relations_by_generation(serializer.data)
+ data_by_gen = [
+ list(group) for _, group in itertools.groupby(serializer_data, key=lambda r: r["generation"]["name"])
+ ]
+ all_types = Type.objects.select_related("generation").all()
+ type_cache = {t.pk: t for t in all_types}
+ name_to_gen_pk = {t.name: t.generation.pk if t.generation else 0 for t in all_types}
# process each generation's data in turn
- final_data = []
- past_relations = {}
+ final_data: list[dict[str, Any]] = []
for gen_data in data_by_gen:
+ current_gen_name = gen_data[0]["generation"]["name"]
+ current_gen = Generation.objects.filter(name=current_gen_name).first()
# create past relations object for this generation
- past_relations = OrderedDict()
-
- # set generation
- past_relations["generation"] = gen_data[0]["generation"]
-
- # use current damage relations object
- past_relations["damage_relations"] = self.get_type_relationships(obj)
+ past_relations: dict[str, Any] = {
+ "generation": gen_data[0]["generation"],
+ "damage_relations": self.get_type_relationships(obj),
+ }
relations = past_relations["damage_relations"]
- current_gen = Generation.objects.get(name=gen_data[0]["generation"]["name"])
-
# remove types not yet introduced
# e.g. Poison has no effect on Steel, but Steel was not present in generation I
# so it should be absent from the list
- relations["no_damage_to"] = self.remove_newer_types(relations["no_damage_to"], current_gen)
- relations["half_damage_to"] = self.remove_newer_types(relations["half_damage_to"], current_gen)
- relations["double_damage_to"] = self.remove_newer_types(relations["double_damage_to"], current_gen)
- relations["no_damage_from"] = self.remove_newer_types(relations["no_damage_from"], current_gen)
- relations["half_damage_from"] = self.remove_newer_types(relations["half_damage_from"], current_gen)
- relations["double_damage_from"] = self.remove_newer_types(relations["double_damage_from"], current_gen)
+ if current_gen:
+ for key in self.RELATION_KEYS:
+ relations[key] = [
+ item for item in relations[key] if name_to_gen_pk.get(item["name"], 0) <= current_gen.pk
+ ]
# populate offensive relations
- results = list(filter(lambda x: x["damage_type"] == obj.id, gen_data))
- for relation in results:
- type = Type.objects.get(pk=relation["target_type"])
-
- # remove conflicting entry if it exists
- self.remove_type_entry(relations, type, direction="_damage_to")
-
- # add entry
- damage_factor = relation["damage_factor"]
- self.add_type_entry(relations, type, damage_factor, direction="_damage_to")
-
- del relation["generation"]
-
+ for relation in (r for r in gen_data if r["damage_type"] == obj.pk):
+ if target_type_obj := type_cache.get(relation["target_type"]):
+ self.remove_type_entry(relations, target_type_obj, direction="_damage_to")
+ self.add_type_entry(relations, target_type_obj, relation["damage_factor"], direction="_damage_to")
# populate defensive relations
- results = list(filter(lambda x: x["target_type"] == obj.id, gen_data))
- for relation in results:
- type = Type.objects.get(pk=relation["damage_type"])
-
- # remove conflicting entry if it exists
- self.remove_type_entry(relations, type, direction="_damage_from")
-
- # add entry
- damage_factor = relation["damage_factor"]
- self.add_type_entry(relations, type, damage_factor, direction="_damage_from")
-
- del relation["generation"]
+ for relation in (r for r in gen_data if r["target_type"] == obj.pk):
+ if damage_type_obj := type_cache.get(relation["damage_type"]):
+ self.remove_type_entry(relations, damage_type_obj, direction="_damage_from")
+ self.add_type_entry(relations, damage_type_obj, relation["damage_factor"], direction="_damage_from")
- # add to final list
final_data.append(past_relations)
return final_data
- def remove_newer_types(self, relations, current_gen):
- return list(filter(lambda x: self.type_is_present(x, current_gen), relations))
-
- def type_is_present(self, type, current_gen):
- type_obj = Type.objects.get(name=type["name"])
- gen_introduced = Generation.objects.get(pk=type_obj.generation.id)
- return gen_introduced.id <= current_gen.id
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["potency", "flavor"],
- "properties": {
- "slot": {"type": "integer", "examples": [1]},
- "pokemon": {
- "type": "object",
- "require": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the pokemon",
- "examples": ["sandshrew"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "description": "The URL to get more information about the pokemon",
- "examples": ["https://pokeapi.co/api/v2/pokemon/27/"],
- },
- },
- },
- },
- },
- }
- )
- def get_type_pokemon(self, obj):
- poke_type_objects = PokemonType.objects.filter(type=obj)
- poke_types = PokemonTypeSerializer(poke_type_objects, many=True, context=self.context).data
-
- for poke_type in poke_types:
- del poke_type["type"]
-
- return poke_types
+ @extend_schema_field(TypePokemonSerializer(many=True))
+ def get_type_pokemon(self, obj: Type) -> ReturnList[ReturnDict[str, Any]]:
+ poke_type_objects = PokemonType.objects.filter(type=obj).select_related("pokemon")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ TypePokemonSerializer(poke_type_objects, many=True, context=self.context).data,
+ )
#########################
# MACHINE SERIALIZERS #
#########################
-class MachineDetailSerializer(serializers.ModelSerializer):
+
+
+class MachineDetailSerializer(serializers.ModelSerializer[Machine]):
item = ItemSummarySerializer()
version_group = VersionGroupSummarySerializer()
move = MoveSummarySerializer()
@@ -2890,7 +2047,9 @@ class Meta:
###################################
# MOVE BATTLE STYLE SERIALIZERS #
###################################
-class MoveBattleStyleNameSerializer(serializers.ModelSerializer):
+
+
+class MoveBattleStyleNameSerializer(serializers.ModelSerializer[MoveBattleStyleName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2898,7 +2057,7 @@ class Meta:
fields = ("name", "language")
-class MoveBattleStyleDetailSerializer(serializers.ModelSerializer):
+class MoveBattleStyleDetailSerializer(serializers.ModelSerializer[MoveBattleStyle]):
names = MoveBattleStyleNameSerializer(many=True, read_only=True, source="movebattlestylename")
class Meta:
@@ -2909,7 +2068,9 @@ class Meta:
###################################
# MOVE DAMAGE CLASS SERIALIZERS #
###################################
-class MoveDamageClassNameSerializer(serializers.ModelSerializer):
+
+
+class MoveDamageClassNameSerializer(serializers.ModelSerializer[MoveDamageClassName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2917,7 +2078,7 @@ class Meta:
fields = ("name", "language")
-class MoveDamageClassDescriptionSerializer(serializers.ModelSerializer):
+class MoveDamageClassDescriptionSerializer(serializers.ModelSerializer[MoveDamageClassDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -2925,7 +2086,7 @@ class Meta:
fields = ("description", "language")
-class MoveDamageClassDetailSerializer(serializers.ModelSerializer):
+class MoveDamageClassDetailSerializer(serializers.ModelSerializer[MoveDamageClass]):
names = MoveDamageClassNameSerializer(many=True, read_only=True, source="movedamageclassname")
descriptions = MoveDamageClassDescriptionSerializer(many=True, read_only=True, source="movedamageclassdescription")
moves = MoveSummarySerializer(many=True, read_only=True, source="move")
@@ -2944,7 +2105,9 @@ class Meta:
###########################
# MOVE META SERIALIZERS #
###########################
-class MoveMetaAilmentNameSerializer(serializers.ModelSerializer):
+
+
+class MoveMetaAilmentNameSerializer(serializers.ModelSerializer[MoveMetaAilmentName]):
language = LanguageSummarySerializer()
class Meta:
@@ -2952,7 +2115,7 @@ class Meta:
fields = ("name", "language")
-class MoveMetaAilmentDetailSerializer(serializers.ModelSerializer):
+class MoveMetaAilmentDetailSerializer(serializers.ModelSerializer[MoveMetaAilment]):
names = MoveMetaAilmentNameSerializer(many=True, read_only=True, source="movemetaailmentname")
moves = serializers.SerializerMethodField("get_ailment_moves")
@@ -2960,36 +2123,16 @@ class Meta:
model = MoveMetaAilment
fields = ("id", "name", "moves", "names")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["thunder-punch"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/9/"],
- },
- },
- },
- }
- )
- def get_ailment_moves(self, obj):
- move_meta_objects = MoveMeta.objects.filter(move_meta_ailment=obj)
- moves = []
-
- for meta in move_meta_objects:
- move_obj = Move.objects.get(pk=meta.move.id)
- data = MoveSummarySerializer(move_obj, context=self.context).data
- moves.append(data)
-
- return moves
+ @extend_schema_field(MoveSummarySerializer(many=True))
+ def get_ailment_moves(self, obj: MoveMetaAilment) -> ReturnList[ReturnDict[str, Any]]:
+ moves = Move.objects.filter(movemeta__move_meta_ailment=obj)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveSummarySerializer(moves, many=True, context=self.context).data,
+ )
-class MoveMetaCategoryDescriptionSerializer(serializers.ModelSerializer):
+class MoveMetaCategoryDescriptionSerializer(serializers.ModelSerializer[MoveMetaCategoryDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -2997,7 +2140,7 @@ class Meta:
fields = ("description", "language")
-class MoveMetaCategoryDetailSerializer(serializers.ModelSerializer):
+class MoveMetaCategoryDetailSerializer(serializers.ModelSerializer[MoveMetaCategory]):
descriptions = MoveMetaCategoryDescriptionSerializer(
many=True, read_only=True, source="movemetacategorydescription"
)
@@ -3007,36 +2150,16 @@ class Meta:
model = MoveMetaCategory
fields = ("id", "name", "descriptions", "moves")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["sing"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/47/"],
- },
- },
- },
- }
- )
- def get_category_moves(self, obj):
- move_meta_objects = MoveMeta.objects.filter(move_meta_category=obj)
- moves = []
-
- for meta in move_meta_objects:
- move_obj = Move.objects.get(pk=meta.move.id)
- data = MoveSummarySerializer(move_obj, context=self.context).data
- moves.append(data)
-
- return moves
+ @extend_schema_field(MoveSummarySerializer(many=True))
+ def get_category_moves(self, obj: MoveMetaCategory) -> ReturnList[ReturnDict[str, Any]]:
+ moves = Move.objects.filter(movemeta__move_meta_category=obj)
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveSummarySerializer(moves, many=True, context=self.context).data,
+ )
-class MoveMetaSerializer(serializers.ModelSerializer):
+class MoveMetaSerializer(serializers.ModelSerializer[MoveMeta]):
ailment = MoveMetaAilmentSummarySerializer(source="move_meta_ailment")
category = MoveMetaCategorySummarySerializer(source="move_meta_category")
@@ -3061,7 +2184,9 @@ class Meta:
#############################
# MOVE TARGET SERIALIZERS #
#############################
-class MoveTargetNameSerializer(serializers.ModelSerializer):
+
+
+class MoveTargetNameSerializer(serializers.ModelSerializer[MoveTargetName]):
language = LanguageSummarySerializer()
class Meta:
@@ -3069,7 +2194,7 @@ class Meta:
fields = ("name", "language")
-class MoveTargetDescriptionSerializer(serializers.ModelSerializer):
+class MoveTargetDescriptionSerializer(serializers.ModelSerializer[MoveTargetDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -3077,7 +2202,7 @@ class Meta:
fields = ("description", "language")
-class MoveTargetDetailSerializer(serializers.ModelSerializer):
+class MoveTargetDetailSerializer(serializers.ModelSerializer[MoveTarget]):
names = MoveTargetNameSerializer(many=True, read_only=True, source="movetargetname")
descriptions = MoveTargetDescriptionSerializer(many=True, read_only=True, source="movetargetdescription")
moves = MoveSummarySerializer(many=True, read_only=True, source="move")
@@ -3090,15 +2215,25 @@ class Meta:
######################
# MOVE SERIALIZERS #
######################
-class MoveNameSerializer(serializers.ModelSerializer):
+
+
+class MoveNameSerializer(serializers.ModelSerializer[MoveName]):
language = LanguageSummarySerializer()
class Meta:
- model = AbilityName
+ model = MoveName
fields = ("name", "language")
-class MoveChangeSerializer(serializers.ModelSerializer):
+class MoveEffectEffectTextSerializer(serializers.ModelSerializer[MoveEffectEffectText]):
+ language = LanguageSummarySerializer()
+
+ class Meta:
+ model = MoveEffectEffectText
+ fields = ("effect", "short_effect", "language")
+
+
+class MoveChangeSerializer(serializers.ModelSerializer[MoveChange]):
version_group = VersionGroupSummarySerializer()
type = TypeSummarySerializer()
effect_entries = serializers.SerializerMethodField("get_effects")
@@ -3116,53 +2251,16 @@ class Meta:
"version_group",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["effect", "short_effect", "language"],
- "properties": {
- "effect": {
- "type": "string",
- "examples": ["Inflicts regular damage."],
- },
- "short_effect": {
- "type": "string",
- "examples": ["Inflicts regular damage with no additional effect."],
- },
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- },
- },
- }
- )
- def get_effects(self, obj):
- effect_texts = MoveEffectEffectText.objects.filter(move_effect=obj.move_effect)
- data = MoveEffectEffectTextSerializer(effect_texts, many=True, context=self.context).data
-
- return data
-
-
-class MoveEffectEffectTextSerializer(serializers.ModelSerializer):
- language = LanguageSummarySerializer()
-
- class Meta:
- model = MoveEffectEffectText
- fields = ("effect", "short_effect", "language")
+ @extend_schema_field(MoveEffectEffectTextSerializer(many=True))
+ def get_effects(self, obj: MoveChange) -> ReturnList[ReturnDict[str, Any]]:
+ effect_texts = MoveEffectEffectText.objects.filter(move_effect=obj.move_effect).select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveEffectEffectTextSerializer(effect_texts, many=True, context=self.context).data,
+ )
-class MoveEffectChangeEffectTextSerializer(serializers.ModelSerializer):
+class MoveEffectChangeEffectTextSerializer(serializers.ModelSerializer[MoveEffectChangeEffectText]):
language = LanguageSummarySerializer()
class Meta:
@@ -3170,7 +2268,7 @@ class Meta:
fields = ("effect", "language")
-class MoveEffectChangeSerializer(serializers.ModelSerializer):
+class MoveEffectChangeSerializer(serializers.ModelSerializer[MoveEffectChange]):
version_group = VersionGroupSummarySerializer()
effect_entries = MoveEffectChangeEffectTextSerializer(
many=True, read_only=True, source="moveeffectchangeeffecttext"
@@ -3181,7 +2279,7 @@ class Meta:
fields = ("version_group", "effect_entries")
-class MoveFlavorTextSerializer(serializers.ModelSerializer):
+class MoveFlavorTextSerializer(serializers.ModelSerializer[MoveFlavorText]):
flavor_text = serializers.CharField()
language = LanguageSummarySerializer()
version_group = VersionGroupSummarySerializer()
@@ -3191,7 +2289,25 @@ class Meta:
fields = ("flavor_text", "language", "version_group")
-class MoveDetailSerializer(serializers.ModelSerializer):
+class MoveComboUsageSerializer(serializers.Serializer[Any]):
+ use_before = MoveSummarySerializer(many=True, allow_null=True)
+ use_after = MoveSummarySerializer(many=True, allow_null=True)
+
+
+class MoveCombosSerializer(serializers.Serializer[Any]):
+ normal = MoveComboUsageSerializer()
+ super = MoveComboUsageSerializer()
+
+
+class MoveMetaStatChangeSerializer(serializers.ModelSerializer[MoveMetaStatChange]):
+ stat = StatSummarySerializer()
+
+ class Meta:
+ model = MoveMetaStatChange
+ fields = ("change", "stat")
+
+
+class MoveDetailSerializer(serializers.ModelSerializer[Move]):
generation = GenerationSummarySerializer()
type = TypeSummarySerializer()
target = MoveTargetSummarySerializer(source="move_target")
@@ -3240,353 +2356,90 @@ class Meta:
"learned_by_pokemon",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["clefairy"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon/35/"],
- },
- },
- },
- }
- )
- def get_learned_by_pokemon(self, obj):
- pokemon_moves = PokemonMove.objects.filter(move_id=obj).order_by("pokemon_id")
-
- pokemon_list = []
-
- pokemon_ids = pokemon_moves.values("pokemon_id").distinct()
-
- for id in pokemon_ids:
- pokemon_object = Pokemon.objects.get(pk=id["pokemon_id"])
- pokemon_data = PokemonSummarySerializer(pokemon_object, context=self.context).data
-
- pokemon_list.append(pokemon_data)
-
- return pokemon_list
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["machine", "version_group"],
- "properties": {
- "machine": {
- "type": "object",
- "required": ["url"],
- "properties": {
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/machine/1/"],
- }
- },
- },
- "version_group": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["sword-shield"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/1/"],
- },
- },
- },
- },
- },
- }
- )
- def get_move_machines(self, obj):
- machine_objects = Machine.objects.filter(move=obj)
-
- machines = []
-
- for machine_object in machine_objects:
- machine_data = MachineSummarySerializer(machine_object, context=self.context).data
-
- version_group_data = VersionGroupSummarySerializer(machine_object.version_group, context=self.context).data
-
- machines.append({"machine": machine_data, "version_group": version_group_data})
-
- return machines
-
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["normal", "super"],
- "properties": {
- "normal": {
- "type": "object",
- "required": ["use_before", "use_after"],
- "properties": {
- "use_before": {
- "type": "array",
- "nullable": True,
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["fire-punch"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/7/"],
- },
- },
- },
- },
- "use_after": {
- "type": "array",
- "nullable": True,
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["ice-punch"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/8/"],
- },
- },
- },
- },
- },
- },
- "super": {
- "type": "object",
- "required": ["use_before", "use_after"],
- "properties": {
- "use_before": {
- "type": "array",
- "nullable": True,
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["night-slash"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/400/"],
- },
- },
- },
- },
- "use_after": {
- "type": "array",
- "nullable": True,
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["focus-energy"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/116/"],
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_combos(self, obj):
- normal_before_objects = ContestCombo.objects.filter(first_move=obj)
- normal_before_data = ContestComboSerializer(normal_before_objects, many=True, context=self.context).data
- normal_after_objects = ContestCombo.objects.filter(second_move=obj)
- normal_after_data = ContestComboSerializer(normal_after_objects, many=True, context=self.context).data
-
- super_before_objects = SuperContestCombo.objects.filter(first_move=obj)
- super_before_data = SuperContestComboSerializer(super_before_objects, many=True, context=self.context).data
- super_after_objects = SuperContestCombo.objects.filter(second_move=obj)
- super_after_data = SuperContestComboSerializer(super_after_objects, many=True, context=self.context).data
-
- details = None
-
- if normal_before_data or normal_after_data or super_before_data or super_after_data:
- details = OrderedDict()
- details["normal"] = OrderedDict()
- details["normal"]["use_before"] = None
- details["normal"]["use_after"] = None
- details["super"] = OrderedDict()
- details["super"]["use_before"] = None
- details["super"]["use_after"] = None
-
- for combo in normal_before_data:
- if details["normal"]["use_before"] is None:
- details["normal"]["use_before"] = []
- details["normal"]["use_before"].append(combo["second_move"])
-
- for combo in normal_after_data:
- if details["normal"]["use_after"] is None:
- details["normal"]["use_after"] = []
- details["normal"]["use_after"].append(combo["first_move"])
-
- for combo in super_before_data:
- if details["super"]["use_before"] is None:
- details["super"]["use_before"] = []
- details["super"]["use_before"].append(combo["second_move"])
-
- for combo in super_after_data:
- if details["super"]["use_after"] is None:
- details["super"]["use_after"] = []
- details["super"]["use_after"].append(combo["first_move"])
-
- return details
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["effect", "short_effect", "language"],
- "properties": {
- "effect": {
- "type": "string",
- "examples": ["Inflicts regular damage."],
- },
- "short_effect": {
- "type": "string",
- "examples": ["Inflicts regular damage with no additional effect."],
- },
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- },
- },
- }
- )
- def get_effect_text(self, obj):
- effect_texts = MoveEffectEffectText.objects.filter(move_effect=obj.move_effect)
- data = MoveEffectEffectTextSerializer(effect_texts, many=True, context=self.context).data
- if len(data) > 0:
- for key, value in data[0].items():
- if "$effect_chance%" in value:
- data[0][key] = value.replace("$effect_chance", f"{obj.move_effect_chance}")
-
- return data
+ @extend_schema_field(PokemonSummarySerializer(many=True))
+ def get_learned_by_pokemon(self, obj: Move) -> ReturnList[ReturnDict[str, Any]]:
+ pokemon = Pokemon.objects.filter(pokemonmove__move=obj).distinct().order_by("id")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSummarySerializer(pokemon, many=True, context=self.context).data,
+ )
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["effect_entries", "version_group"],
- "properties": {
- "effect_entries": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["effect", "language"],
- "properties": {
- "effect": {
- "type": "string",
- "examples": ["Hits Pokémon under the effects of dig and fly."],
- },
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- },
- },
- },
- "version_group": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["gold-silver"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/3/"],
- },
- },
- },
- },
- },
- }
- )
- def get_effect_change_text(self, obj):
- effect_changes = MoveEffectChange.objects.filter(move_effect=obj.move_effect)
- data = MoveEffectChangeSerializer(effect_changes, many=True, context=self.context).data
+ @extend_schema_field(ItemMachineSerializer(many=True))
+ def get_move_machines(self, obj: Move) -> ReturnList[ReturnDict[str, Any]]:
+ machine_objects = Machine.objects.filter(move=obj).select_related("version_group")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ ItemMachineSerializer(machine_objects, many=True, context=self.context).data,
+ )
- return data
+ @extend_schema_field(MoveCombosSerializer(allow_null=True))
+ def get_combos(self, obj: Move) -> dict[str, Any] | None:
+ normal_before = [
+ c.second_move
+ for c in ContestCombo.objects.filter(first_move=obj).select_related("second_move")
+ if c.second_move
+ ]
+ normal_after = [
+ c.first_move
+ for c in ContestCombo.objects.filter(second_move=obj).select_related("first_move")
+ if c.first_move
+ ]
+ super_before = [
+ c.second_move
+ for c in SuperContestCombo.objects.filter(first_move=obj).select_related("second_move")
+ if c.second_move
+ ]
+ super_after = [
+ c.first_move
+ for c in SuperContestCombo.objects.filter(second_move=obj).select_related("first_move")
+ if c.first_move
+ ]
+
+ if not (normal_before or normal_after or super_before or super_after):
+ return None
+
+ def serialize_list(moves: list[Any]) -> list[dict[str, Any]] | None:
+ if not moves:
+ return None
+ return cast(
+ "list[dict[str, Any]]",
+ MoveSummarySerializer(moves, many=True, context=self.context).data,
+ )
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["change", "stat"],
- "properties": {
- "change": {"type": "integer", "format": "int32", "examples": [2]},
- "stat": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["attack"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/stat/1/"],
- },
- },
- },
- },
+ return {
+ "normal": {
+ "use_before": serialize_list(normal_before),
+ "use_after": serialize_list(normal_after),
+ },
+ "super": {
+ "use_before": serialize_list(super_before),
+ "use_after": serialize_list(super_after),
},
}
- )
- def get_move_stat_change(self, obj):
- stat_change_objects = MoveMetaStatChange.objects.filter(move=obj)
- stat_changes = MoveMetaStatChangeSerializer(stat_change_objects, many=True, context=self.context).data
- for change in stat_changes:
- del change["move"]
+ @extend_schema_field(MoveEffectEffectTextSerializer(many=True))
+ def get_effect_text(self, obj: Move) -> ReturnList[ReturnDict[str, Any]]:
+ effect_texts = MoveEffectEffectText.objects.filter(move_effect=obj.move_effect).select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveEffectEffectTextSerializer(effect_texts, many=True, context=self.context).data,
+ )
+
+ @extend_schema_field(MoveEffectChangeSerializer(many=True))
+ def get_effect_change_text(self, obj: Move) -> ReturnList[ReturnDict[str, Any]]:
+ effect_changes = MoveEffectChange.objects.filter(move_effect=obj.move_effect).select_related("version_group")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveEffectChangeSerializer(effect_changes, many=True, context=self.context).data,
+ )
- return stat_changes
+ @extend_schema_field(MoveMetaStatChangeSerializer(many=True))
+ def get_move_stat_change(self, obj: Move) -> ReturnList[ReturnDict[str, Any]]:
+ stat_changes = MoveMetaStatChange.objects.filter(move=obj).select_related("stat")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveMetaStatChangeSerializer(stat_changes, many=True, context=self.context).data,
+ )
##########################
@@ -3594,24 +2447,23 @@ def get_move_stat_change(self, obj):
##########################
-class PalParkSerializer(serializers.ModelSerializer):
- area = PalParkAreaSummarySerializer(read_only=True, source="pal_park_area")
- pokemon_species = PokemonSpeciesSummarySerializer()
+class PalParkAreaNameSerializer(serializers.HyperlinkedModelSerializer[PalParkAreaName]):
+ language = LanguageSummarySerializer()
class Meta:
- model = PalPark
- fields = ("base_score", "rate", "area", "pokemon_species")
+ model = PalParkAreaName
+ fields = ("name", "language")
-class PalParkAreaNameSerializer(serializers.HyperlinkedModelSerializer):
- language = LanguageSummarySerializer()
+class PalParkEncounterSerializer(serializers.ModelSerializer[PalPark]):
+ pokemon_species = PokemonSpeciesSummarySerializer()
class Meta:
- model = PalParkAreaName
- fields = ("name", "language")
+ model = PalPark
+ fields = ("base_score", "rate", "pokemon_species")
-class PalParkAreaDetailSerializer(serializers.ModelSerializer):
+class PalParkAreaDetailSerializer(serializers.ModelSerializer[PalParkArea]):
names = PalParkAreaNameSerializer(many=True, read_only=True, source="palparkareaname")
pokemon_encounters = serializers.SerializerMethodField("get_encounters")
@@ -3619,45 +2471,14 @@ class Meta:
model = PalParkArea
fields = ("id", "name", "names", "pokemon_encounters")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["base_score", "pokemon-species", "rate"],
- "properties": {
- "base_score": {
- "type": "integer",
- "format": "int32",
- "examples": [50],
- },
- "pokemon-species": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bulbasaur"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/1/"],
- },
- },
- },
- "rate": {"type": "integer", "format": "int32", "examples": [30]},
- },
- },
- }
- )
- def get_encounters(self, obj):
- pal_park_objects = PalPark.objects.filter(pal_park_area=obj)
- parks = PalParkSerializer(pal_park_objects, many=True, context=self.context).data
- encounters = []
-
- for encounter in parks:
- del encounter["area"]
- encounters.append(encounter)
+ @extend_schema_field(PalParkEncounterSerializer(many=True))
+ def get_encounters(self, obj: PalParkArea) -> ReturnList[ReturnDict[str, Any]]:
+ pal_park_objects = PalPark.objects.filter(pal_park_area=obj).select_related("pokemon_species")
- return encounters
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PalParkEncounterSerializer(pal_park_objects, many=True, context=self.context).data,
+ )
###############################
@@ -3665,7 +2486,7 @@ def get_encounters(self, obj):
###############################
-class PokemonColorNameSerializer(serializers.HyperlinkedModelSerializer):
+class PokemonColorNameSerializer(serializers.HyperlinkedModelSerializer[PokemonColorName]):
language = LanguageSummarySerializer()
class Meta:
@@ -3673,7 +2494,7 @@ class Meta:
fields = ("name", "language")
-class PokemonColorDetailSerializer(serializers.ModelSerializer):
+class PokemonColorDetailSerializer(serializers.ModelSerializer[PokemonColor]):
names = PokemonColorNameSerializer(many=True, read_only=True, source="pokemoncolorname")
pokemon_species = PokemonSpeciesSummarySerializer(many=True, read_only=True, source="pokemonspecies")
@@ -3685,21 +2506,15 @@ class Meta:
##############################
# POKEMON FORM SERIALIZERS #
##############################
-class PokemonFormSpritesSerializer(serializers.ModelSerializer):
- class Meta:
- model = PokemonFormSprites
- fields = ("sprites",)
-class PokemonFormNameSerializer(serializers.ModelSerializer):
- language = LanguageSummarySerializer()
-
+class PokemonFormSpritesSerializer(serializers.ModelSerializer[PokemonFormSprites]):
class Meta:
- model = PokemonFormName
- fields = ("name", "pokemon_name", "language")
+ model = PokemonFormSprites
+ fields = ("sprites",)
-class PokemonFormConditionSerializer(serializers.ModelSerializer):
+class PokemonFormConditionSerializer(serializers.ModelSerializer[PokemonFormCondition]):
trigger = serializers.CharField(source="form_trigger.name", read_only=True)
item = ItemSummarySerializer()
ability = AbilitySummarySerializer()
@@ -3711,7 +2526,52 @@ class Meta:
fields = ("trigger", "item", "ability", "move", "base_form")
-class PokemonFormDetailSerializer(serializers.ModelSerializer):
+class PokemonFormNameSerializer(serializers.ModelSerializer[PokemonFormName]):
+ language = LanguageSummarySerializer()
+
+ class Meta:
+ model = PokemonFormName
+ fields = ("name", "language")
+
+
+class PokemonFormTypeSerializer(serializers.ModelSerializer[PokemonFormType]):
+ type = TypeSummarySerializer()
+
+ class Meta:
+ model = PokemonFormType
+ fields = ("slot", "type")
+
+
+class PokemonTypeSerializer(serializers.ModelSerializer[PokemonType]):
+ type = TypeSummarySerializer()
+
+ class Meta:
+ model = PokemonType
+ fields = ("slot", "type")
+
+
+class PokemonFormTriggerConditionSerializer(serializers.Serializer[Any]):
+ trigger = serializers.CharField()
+ base_form = PokemonFormSummarySerializer()
+
+
+class PokemonShapeNameSerializer(serializers.ModelSerializer[PokemonShapeName]):
+ language = LanguageSummarySerializer()
+
+ class Meta:
+ model = PokemonShapeName
+ fields = ("name", "language")
+
+
+class PokemonShapeAwesomeNameSerializer(serializers.ModelSerializer[PokemonShapeName]):
+ language = LanguageSummarySerializer()
+
+ class Meta:
+ model = PokemonShapeName
+ fields = ("awesome_name", "language")
+
+
+class PokemonFormDetailSerializer(serializers.ModelSerializer[PokemonForm]):
pokemon = PokemonSummarySerializer()
version_group = VersionGroupSummarySerializer()
sprites = serializers.SerializerMethodField("get_pokemon_form_sprites")
@@ -3740,214 +2600,72 @@ class Meta:
"trigger_conditions",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["language", "name"],
- "properties": {
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- "name": {"type": "string", "examples": ["Plant Cloak"]},
- },
- },
- }
- )
- def get_pokemon_form_names(self, obj):
- form_results = PokemonFormName.objects.filter(pokemon_form=obj, name__regex=".+")
- form_serializer = PokemonFormNameSerializer(form_results, many=True, context=self.context)
+ @extend_schema_field(PokemonFormNameSerializer(many=True))
+ def get_pokemon_form_names(self, obj: PokemonForm) -> ReturnList[ReturnDict[str, Any]]:
+ form_results = PokemonFormName.objects.filter(pokemon_form=obj, name__regex=".+").select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonFormNameSerializer(form_results, many=True, context=self.context).data,
+ )
- data = form_serializer.data
-
- for name in data:
- del name["pokemon_name"]
+ @extend_schema_field(PokemonFormNameSerializer(many=True))
+ def get_pokemon_form_pokemon_names(self, obj: PokemonForm) -> ReturnList[ReturnDict[str, Any]]:
+ form_results = PokemonFormName.objects.filter(pokemon_form=obj, pokemon_name__regex=".+").select_related(
+ "language"
+ )
+ data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonFormNameSerializer(form_results, many=True, context=self.context).data,
+ )
+ for item, fn in zip(data, form_results, strict=True):
+ item["name"] = fn.pokemon_name
return data
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["language", "name"],
- "properties": {
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- "name": {"type": "string", "examples": ["Plant Cloak"]},
- },
- },
- }
- )
- def get_pokemon_form_pokemon_names(self, obj):
- form_results = PokemonFormName.objects.filter(pokemon_form=obj, pokemon_name__regex=".+")
- form_serializer = PokemonFormNameSerializer(form_results, many=True, context=self.context)
+ @extend_schema_field(PokemonFormSpritesSerializer)
+ def get_pokemon_form_sprites(self, obj: PokemonForm) -> dict[str, Any]:
+ sprites_object = PokemonFormSprites.objects.filter(pokemon_form=obj).first()
+ return sprites_object.sprites if sprites_object else {}
- data = form_serializer.data
+ @extend_schema_field(PokemonFormTypeSerializer(many=True))
+ def get_pokemon_form_types(self, obj: PokemonForm) -> ReturnList[ReturnDict[str, Any]]:
+ form_types = PokemonFormType.objects.filter(pokemon_form=obj).select_related("type").order_by("slot")
- for name in data:
- name["name"] = name["pokemon_name"]
- del name["pokemon_name"]
+ if form_types:
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonFormTypeSerializer(form_types, many=True, context=self.context).data,
+ )
- return data
+ # Fall back to parent Pokemon's types if no form-specific types exist
+ pokemon_types = PokemonType.objects.filter(pokemon=obj.pokemon).select_related("type").order_by("slot")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonTypeSerializer(pokemon_types, many=True, context=self.context).data,
+ )
- @extend_schema_field(
- field={
- "type": "object",
- "properties": {
- "default": {
- "type": "string",
- "format": "uri",
- "examples": [
- "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/412.png"
- ],
- }
- },
- "additionalProperties": { # Stoplight Elements doesn't render this well
- "type": "string",
- "format": "uri",
- "nullable": True,
- "examples": [
- "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/412.png"
- ],
- },
- "examples": [
- {
- "back_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/412.png",
- "back_female": None,
- "back_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/412.png",
- "back_shiny_female": None,
- "front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/412.png",
- "front_female": None,
- "front_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/412.png",
- "front_shiny_female": None,
- }
- ],
- }
- )
- def get_pokemon_form_sprites(self, obj):
- sprites_object = PokemonFormSprites.objects.get(pokemon_form_id=obj)
- return sprites_object.sprites
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["slot", "type"],
- "properties": {
- "slot": {"type": "integer", "format": "int32", "examples": [1]},
- "type": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bug"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/7/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_form_types(self, obj):
- form_type_objects = PokemonFormType.objects.filter(pokemon_form=obj)
- form_types = PokemonFormTypeSerializer(form_type_objects, many=True, context=self.context).data
-
- for form_type in form_types:
- del form_type["pokemon_form"]
-
- # defer to parent Pokemon's types if no form-specific types
- if form_types == []:
- pokemon_object = Pokemon.objects.get(id=obj.pokemon_id)
- pokemon_type_objects = PokemonType.objects.filter(pokemon=pokemon_object)
- form_types = PokemonTypeSerializer(pokemon_type_objects, many=True, context=self.context).data
-
- for form_type in form_types:
- del form_type["pokemon"]
-
- return form_types
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["trigger", "name", "url"],
- "properties": {
- "trigger": {
- "type": "string",
- "examples": ["held-item"],
- },
- "name": {
- "type": "string",
- "examples": ["venusaurite"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/item/698/"],
- },
- "base_form": {
- "type": "object",
- "nullable": True,
- "properties": {
- "name": {
- "type": "string",
- "examples": ["necrozma-dusk"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-form/10314/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_form_triggers_conditions(self, obj):
- conditions = PokemonFormCondition.objects.filter(pokemon_form=obj)
- conditions_data = PokemonFormConditionSerializer(conditions, many=True, context=self.context).data
+ @extend_schema_field(PokemonFormTriggerConditionSerializer(many=True))
+ def get_pokemon_form_triggers_conditions(self, obj: PokemonForm) -> list[dict[str, Any]]:
+ conditions = PokemonFormCondition.objects.filter(pokemon_form=obj).select_related(
+ "form_trigger", "item", "ability", "move", "base_form"
+ )
+ conditions_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonFormConditionSerializer(conditions, many=True, context=self.context).data,
+ )
- triggers = []
+ triggers: list[dict[str, Any]] = []
for condition in conditions_data:
- trigger_value = condition.pop("trigger", None)
- if not trigger_value:
- continue
- base_form = condition.pop("base_form", None)
- trigger = {"trigger": trigger_value}
- for value in condition.values():
- if value:
- trigger.update(value)
- break
- if base_form:
- trigger["base_form"] = base_form
- triggers.append(trigger)
+ if trigger_value := condition.get("trigger"):
+ trigger = {"trigger": trigger_value}
+ for key, value in condition.items():
+ if key not in ("trigger", "base_form") and value:
+ trigger.update(value)
+ break
+ if base_form := condition.get("base_form"):
+ trigger["base_form"] = base_form
+ triggers.append(trigger)
+
return triggers
@@ -3956,7 +2674,7 @@ def get_pokemon_form_triggers_conditions(self, obj):
#################################
-class PokemonHabitatNameSerializer(serializers.HyperlinkedModelSerializer):
+class PokemonHabitatNameSerializer(serializers.HyperlinkedModelSerializer[PokemonHabitatName]):
language = LanguageSummarySerializer()
class Meta:
@@ -3964,7 +2682,7 @@ class Meta:
fields = ("name", "language")
-class PokemonHabitatDetailSerializer(serializers.ModelSerializer):
+class PokemonHabitatDetailSerializer(serializers.ModelSerializer[PokemonHabitat]):
names = PokemonHabitatNameSerializer(many=True, read_only=True, source="pokemonhabitatname")
pokemon_species = PokemonSpeciesSummarySerializer(many=True, read_only=True, source="pokemonspecies")
@@ -3978,7 +2696,7 @@ class Meta:
##############################
-class MoveLearnMethodNameSerializer(serializers.HyperlinkedModelSerializer):
+class MoveLearnMethodNameSerializer(serializers.HyperlinkedModelSerializer[MoveLearnMethodName]):
language = LanguageSummarySerializer()
class Meta:
@@ -3986,7 +2704,7 @@ class Meta:
fields = ("name", "language")
-class MoveLearnMethodDescriptionSerializer(serializers.HyperlinkedModelSerializer):
+class MoveLearnMethodDescriptionSerializer(serializers.HyperlinkedModelSerializer[MoveLearnMethodDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -3994,7 +2712,7 @@ class Meta:
fields = ("description", "language")
-class MoveLearnMethodDetailSerializer(serializers.ModelSerializer):
+class MoveLearnMethodDetailSerializer(serializers.ModelSerializer[MoveLearnMethod]):
names = MoveLearnMethodNameSerializer(many=True, read_only=True, source="movelearnmethodname")
descriptions = MoveLearnMethodDescriptionSerializer(many=True, read_only=True, source="movelearnmethoddescription")
version_groups = serializers.SerializerMethodField("get_method_version_groups")
@@ -4003,61 +2721,21 @@ class Meta:
model = MoveLearnMethod
fields = ("id", "name", "names", "descriptions", "version_groups")
- # "version_groups": [
- # {
- # "name": "red-blue",
- # "url": "https://pokeapi.co/api/v2/version-group/1/"
- # },
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["red-blue"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/1/"],
- },
- },
- },
- }
- )
- def get_method_version_groups(self, obj):
- version_group_objects = VersionGroupMoveLearnMethod.objects.filter(move_learn_method=obj)
- version_group_data = VersionGroupMoveLearnMethodSerializer(
- version_group_objects, many=True, context=self.context
- ).data
- groups = []
-
- for vg in version_group_data:
- groups.append(vg["version_group"])
-
- return groups
-
-
-# https://stackoverflow.com/a/45987450/3482533
-class PokemonMoveSerializer(serializers.ModelSerializer):
- class Meta:
- model = PokemonMove
- fields = "__all__"
+ @extend_schema_field(VersionGroupSummarySerializer(many=True))
+ def get_method_version_groups(self, obj: MoveLearnMethod) -> ReturnList[ReturnDict[str, Any]]:
+ version_groups = VersionGroup.objects.filter(versiongroupmovelearnmethod__move_learn_method=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ VersionGroupSummarySerializer(version_groups, many=True, context=self.context).data,
+ )
###############################
# POKEMON SHAPE SERIALIZERS #
###############################
-class PokemonShapeNameSerializer(serializers.HyperlinkedModelSerializer):
- language = LanguageSummarySerializer()
-
- class Meta:
- model = PokemonShapeName
- fields = ("name", "awesome_name", "language")
-class PokemonShapeDetailSerializer(serializers.ModelSerializer):
+class PokemonShapeDetailSerializer(serializers.ModelSerializer[PokemonShape]):
names = serializers.SerializerMethodField("get_shape_names")
awesome_names = serializers.SerializerMethodField("get_shape_awesome_names")
pokemon_species = PokemonSpeciesSummarySerializer(many=True, read_only=True, source="pokemonspecies")
@@ -4066,100 +2744,89 @@ class Meta:
model = PokemonShape
fields = ("id", "name", "awesome_names", "names", "pokemon_species")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["url", "name"],
- "properties": {
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- "name": {"type": "string", "examples": ["Ball"]},
- },
- },
- }
- )
- def get_shape_names(self, obj):
- results = PokemonShapeName.objects.filter(pokemon_shape_id=obj)
- serializer = PokemonShapeNameSerializer(results, many=True, context=self.context)
- data = serializer.data
-
- for entry in data:
- del entry["awesome_name"]
-
- return data
+ @extend_schema_field(PokemonShapeNameSerializer(many=True))
+ def get_shape_names(self, obj: PokemonShape) -> ReturnList[ReturnDict[str, Any]]:
+ results = PokemonShapeName.objects.filter(pokemon_shape_id=obj).select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonShapeNameSerializer(results, many=True, context=self.context).data,
+ )
- # "awesome_names": [
- # {
- # "awesome_name": "Pomacé",
- # "language": {
- # "name": "fr",
- # "url": "https://pokeapi.co/api/v2/language/5/"
- # }
- # },
- # {
- # "awesome_name": "Pomaceous",
- # "language": {
- # "name": "en",
- # "url": "https://pokeapi.co/api/v2/language/9/"
- # }
- # }
- # ],
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["awesome_name", "language"],
- "properties": {
- "awesome_name": {"type": "string", "examples": ["Pomaceous"]},
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- },
- },
- }
- )
- def get_shape_awesome_names(self, obj):
- results = PokemonShapeName.objects.filter(pokemon_shape_id=obj)
- serializer = PokemonShapeNameSerializer(results, many=True, context=self.context)
- data = serializer.data
+ @extend_schema_field(PokemonShapeAwesomeNameSerializer(many=True))
+ def get_shape_awesome_names(self, obj: PokemonShape) -> ReturnList[ReturnDict[str, Any]]:
+ results = PokemonShapeName.objects.filter(pokemon_shape_id=obj).select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonShapeAwesomeNameSerializer(results, many=True, context=self.context).data,
+ )
- for entry in data:
- del entry["name"]
- return data
+##############################
+# POKEMON ITEM SERIALIZERS #
+##############################
##############################
-# POKEMON ITEM SERIALIZERS #
+# POKEMON STAT SERIALIZERS #
##############################
-class PokemonItemSerializer(serializers.ModelSerializer):
+
+
+#########################
+# POKEMON SERIALIZERS #
+#########################
+
+
+class PokemonGameIndexSerializer(serializers.ModelSerializer[PokemonGameIndex]):
version = VersionSummarySerializer()
- item = ItemSummarySerializer()
class Meta:
- model = PokemonItem
- fields = ("rarity", "item", "version")
+ model = PokemonGameIndex
+ fields = ("game_index", "version")
-##############################
-# POKEMON STAT SERIALIZERS #
-##############################
-class PokemonStatSerializer(serializers.ModelSerializer):
+class PokemonAbilitySerializer(serializers.ModelSerializer[PokemonAbility]):
+ ability = AbilitySummarySerializer()
+
+ class Meta:
+ model = PokemonAbility
+ fields = ("is_hidden", "slot", "ability")
+
+
+class PokemonSpritesSerializer(serializers.Serializer[Any]):
+ front_default = serializers.CharField(allow_null=True)
+
+
+class PokemonCriesSerializer(serializers.Serializer[Any]):
+ latest = serializers.CharField(allow_null=True)
+ legacy = serializers.CharField(allow_null=True)
+
+
+class PokemonMoveVersionGroupSerializer(serializers.Serializer[Any]):
+ level_learned_at = serializers.IntegerField()
+ move_learn_method = MoveLearnMethodSummarySerializer()
+ version_group = VersionGroupSummarySerializer()
+ order = serializers.IntegerField(required=False)
+
+
+class PokemonMoveSerializer(serializers.Serializer[Any]):
+ move = MoveSummarySerializer()
+ version_group_details = PokemonMoveVersionGroupSerializer(many=True)
+
+
+class PokemonAbilityPastSerializer(serializers.ModelSerializer[PokemonAbilityPast]):
+ ability = AbilitySummarySerializer()
+
+ class Meta:
+ model = PokemonAbilityPast
+ fields = ("is_hidden", "slot", "ability")
+
+
+class PokemonPastAbilitySerializer(serializers.Serializer[Any]):
+ abilities = PokemonAbilityPastSerializer(many=True)
+ generation = GenerationSummarySerializer()
+
+
+class PokemonStatSerializer(serializers.ModelSerializer[PokemonStat]):
stat = StatSummarySerializer()
class Meta:
@@ -4167,29 +2834,62 @@ class Meta:
fields = ("base_stat", "effort", "stat")
-class PokemonStatPastSerializer(serializers.ModelSerializer):
+class PokemonPastStatSerializer(serializers.Serializer[Any]):
generation = GenerationSummarySerializer()
- stat = StatSummarySerializer()
+ stats = PokemonStatSerializer(many=True)
+
+
+class PokemonPastTypeSerializer(serializers.Serializer[Any]):
+ generation = GenerationSummarySerializer()
+ types = TypePokemonSerializer(many=True)
+
+
+class PokemonSpeciesNameSerializer(serializers.ModelSerializer[PokemonSpeciesName]):
+ language = LanguageSummarySerializer()
class Meta:
- model = PokemonStatPast
- fields = ("base_stat", "effort", "generation", "stat")
+ model = PokemonSpeciesName
+ fields = ("name", "language")
-#########################
-# POKEMON SERIALIZERS #
-#########################
+class PokemonSpeciesGenusSerializer(serializers.ModelSerializer[PokemonSpeciesName]):
+ language = LanguageSummarySerializer()
+ class Meta:
+ model = PokemonSpeciesName
+ fields = ("genus", "language")
-class PokemonGameIndexSerializer(serializers.ModelSerializer):
- version = VersionSummarySerializer()
+
+class PokemonSpeciesVarietySerializer(serializers.Serializer[Any]):
+ is_default = serializers.BooleanField()
+ pokemon = PokemonSummarySerializer()
+
+
+class PokemonSpeciesPalParkEncounterSerializer(serializers.ModelSerializer[PalPark]):
+ area = PalParkAreaSummarySerializer(source="pal_park_area")
class Meta:
- model = PokemonGameIndex
- fields = ("game_index", "version")
+ model = PalPark
+ fields = ("base_score", "rate", "area")
-class PokemonDetailSerializer(serializers.ModelSerializer):
+class PokemonStatPastSerializer(serializers.ModelSerializer[PokemonStatPast]):
+ stat = StatSummarySerializer()
+
+ class Meta:
+ model = PokemonStatPast
+ fields = ("base_stat", "effort", "stat")
+
+
+class PokemonTypePastSerializer(serializers.ModelSerializer[PokemonTypePast]):
+ type = TypeSummarySerializer()
+
+ class Meta:
+ model = PokemonTypePast
+ fields = ("slot", "type")
+
+
+class PokemonDetailSerializer(serializers.ModelSerializer[Pokemon]):
abilities = serializers.SerializerMethodField("get_pokemon_abilities")
past_abilities = serializers.SerializerMethodField("get_past_pokemon_abilities")
game_indices = PokemonGameIndexSerializer(many=True, read_only=True, source="pokemongameindex")
@@ -4231,944 +2931,205 @@ class Meta:
"past_types",
)
- @extend_schema_field(
- field={
- "type": "object",
- "properties": {
- "front_default": {
- "type": "string",
- "format": "uri",
- "exmaple": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png",
+ @extend_schema_field(PokemonSpritesSerializer)
+ def get_pokemon_sprites(self, obj: Pokemon) -> dict[str, str | None]:
+ sprites_list = list(cast("PokemonWithRelations", obj).pokemonsprites.all())
+ return sprites_list[0].sprites if sprites_list else {}
+
+ @extend_schema_field(PokemonCriesSerializer)
+ def get_pokemon_cries(self, obj: Pokemon) -> dict[str, str | None]:
+ cries_list = list(cast("PokemonWithRelations", obj).pokemoncries.all())
+ return cries_list[0].cries if cries_list else {}
+
+ @extend_schema_field(PokemonMoveSerializer(many=True))
+ def get_pokemon_moves(self, obj: Pokemon) -> list[dict[str, Any]]:
+ pokemon_moves = (
+ PokemonMove.objects.filter(pokemon=obj, move__isnull=False)
+ .select_related("move", "version_group", "move_learn_method")
+ .order_by("move__id", "version_group_id")
+ )
+
+ vg_cache: dict[int, Any] = {}
+ mlm_cache: dict[int, Any] = {}
+
+ moves_grouped: dict[int, dict[str, Any]] = {}
+ for pm in pokemon_moves:
+ if pm.move is None or pm.version_group is None or pm.move_learn_method is None:
+ continue
+
+ move_pk = pm.move.pk
+ if move_pk not in moves_grouped:
+ moves_grouped[move_pk] = {
+ "move": MoveSummarySerializer(pm.move, context=self.context).data,
+ "version_group_details": [],
}
- },
- "additionalProperties": { # Stoplight Elements doesn't render this well
- "type": "string",
- "format": "uri",
- "nullable": True,
- "examples": [
- "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png"
- ],
- },
- "examples": [
+
+ vg_pk = pm.version_group.pk
+ if vg_pk not in vg_cache:
+ vg_cache[vg_pk] = VersionGroupSummarySerializer(pm.version_group, context=self.context).data
+ version_group_data = vg_cache[vg_pk]
+
+ mlm_pk = pm.move_learn_method.pk
+ if mlm_pk not in mlm_cache:
+ mlm_cache[mlm_pk] = MoveLearnMethodSummarySerializer(pm.move_learn_method, context=self.context).data
+ move_learn_method_data = mlm_cache[mlm_pk]
+
+ moves_grouped[move_pk]["version_group_details"].append(
{
- "back_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png",
- "back_female": None,
- "back_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png",
- "back_shiny_female": None,
- "front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png",
- "front_female": None,
- "front_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png",
- "front_shiny_female": None,
+ "level_learned_at": pm.level,
+ "version_group": version_group_data,
+ "move_learn_method": move_learn_method_data,
+ "order": pm.order,
}
- ],
- }
- )
- def get_pokemon_sprites(self, obj):
- sprites_object = PokemonSprites.objects.get(pokemon_id=obj)
- return sprites_object.sprites
-
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["latest", "legacy"],
- "properties": {
- "latest": {
- "type": "string",
- "format": "uri",
- "examples": ["https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/50.ogg"],
- },
- "legacy": {
- "type": "string",
- "format": "uri",
- "examples": ["https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/legacy/50.ogg"],
- },
- },
- }
- )
- def get_pokemon_cries(self, obj):
- cries_object = PokemonCries.objects.get(pokemon_id=obj)
- return cries_object.cries
-
- # {
- # "move": {
- # "name": "scratch",
- # "url": "https://pokeapi.co/api/v2/move/10/"
- # },
- # "version_group_details": [
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "red-blue",
- # "url": "https://pokeapi.co/api/v2/version-group/1/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "yellow",
- # "url": "https://pokeapi.co/api/v2/version-group/2/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "gold-silver",
- # "url": "https://pokeapi.co/api/v2/version-group/3/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "crystal",
- # "url": "https://pokeapi.co/api/v2/version-group/4/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "ruby-sapphire",
- # "url": "https://pokeapi.co/api/v2/version-group/5/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "emerald",
- # "url": "https://pokeapi.co/api/v2/version-group/6/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "firered-leafgreen",
- # "url": "https://pokeapi.co/api/v2/version-group/7/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "diamond-pearl",
- # "url": "https://pokeapi.co/api/v2/version-group/8/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "platinum",
- # "url": "https://pokeapi.co/api/v2/version-group/9/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "heartgold-soulsilver",
- # "url": "https://pokeapi.co/api/v2/version-group/10/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "black-white",
- # "url": "https://pokeapi.co/api/v2/version-group/11/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "colosseum",
- # "url": "https://pokeapi.co/api/v2/version-group/12/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "xd",
- # "url": "https://pokeapi.co/api/v2/version-group/13/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "black-2-white-2",
- # "url": "https://pokeapi.co/api/v2/version-group/14/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "x-y",
- # "url": "https://pokeapi.co/api/v2/version-group/15/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "omega-ruby-alpha-sapphire",
- # "url": "https://pokeapi.co/api/v2/version-group/16/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "sun-moon",
- # "url": "https://pokeapi.co/api/v2/version-group/17/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "ultra-sun-ultra-moon",
- # "url": "https://pokeapi.co/api/v2/version-group/18/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "lets-go-pikachu-lets-go-eevee",
- # "url": "https://pokeapi.co/api/v2/version-group/19/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "sword-shield",
- # "url": "https://pokeapi.co/api/v2/version-group/20/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "brilliant-diamond-shining-pearl",
- # "url": "https://pokeapi.co/api/v2/version-group/23/"
- # }
- # },
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "scarlet-violet",
- # "url": "https://pokeapi.co/api/v2/version-group/25/"
- # }
- # }
- # ]
- # },
-
- # "move": {
- # "name": "scratch",
- # "url": "https://pokeapi.co/api/v2/move/10/"
- # },
- # "version_group_details": [
- # {
- # "level_learned_at": 1,
- # "move_learn_method": {
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- # },
- # "version_group": {
- # "name": "red-blue",
- # "url": "https://pokeapi.co/api/v2/version-group/1/"
- # }
- # },
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["move", "version_group_details"],
- "properties": {
- "move": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["scratch"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move/10/"],
- },
- },
- },
- "version_group_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [
- "level_learned_at",
- "move_learn_method",
- "version_group",
- ],
- "properties": {
- "level_learned_at": {
- "type": "integer",
- "format": "int32",
- "examples": [1],
- },
- "move_learn_method": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["level-up"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move-learn-method/1/"],
- },
- },
- },
- "version_group": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["red-blue"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/1/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_moves(self, obj):
- version_objects = VersionGroup.objects.all().order_by("id")
- version_data = {
- version_object.id: data
- for version_object, data in zip(
- version_objects,
- VersionGroupSummarySerializer(version_objects, many=True, context=self.context).data,
)
- }
- method_objects = MoveLearnMethod.objects.all().order_by("id")
- method_data = {
- method_object.id: data
- for method_object, data in zip(
- method_objects,
- MoveLearnMethodSummarySerializer(method_objects, many=True, context=self.context).data,
+
+ return list(moves_grouped.values())
+
+ @extend_schema_field(PokemonHeldItemSerializer(many=True))
+ def get_pokemon_held_items(self, obj: Pokemon) -> list[dict[str, Any]]:
+ pokemon_items = (
+ PokemonItem.objects.filter(pokemon=obj, item__isnull=False)
+ .select_related("item", "version")
+ .order_by("item__id", "version_id")
+ )
+
+ version_cache: dict[int, Any] = {}
+
+ items_grouped: dict[int, dict[str, Any]] = {}
+ for pi in pokemon_items:
+ if pi.item is None or pi.version is None:
+ continue
+
+ item_pk = pi.item.pk
+ if item_pk not in items_grouped:
+ items_grouped[item_pk] = {
+ "item": ItemSummarySerializer(pi.item, context=self.context).data,
+ "version_details": [],
+ }
+
+ v_pk = pi.version.pk
+ if v_pk not in version_cache:
+ version_cache[v_pk] = VersionSummarySerializer(pi.version, context=self.context).data
+
+ items_grouped[item_pk]["version_details"].append(
+ {
+ "rarity": pi.rarity,
+ "version": version_cache[v_pk],
+ }
)
- }
- # Get moves related to this pokemon and pull out unique Move IDs.
- # Note that it's important to order by the same column we're using to
- # determine if the entries are unique. Otherwise distinct() will
- # return apparent duplicates.
-
- pokemon_moves = PokemonMove.objects.filter(pokemon_id=obj).order_by("move_id")
- move_ids = pokemon_moves.values("move_id").distinct()
- move_list = []
-
- for id in move_ids:
- pokemon_move_details = OrderedDict()
-
- # Get each Unique Move by ID
- move_object = Move.objects.get(pk=id["move_id"])
- move_data = MoveSummarySerializer(move_object, context=self.context).data
- pokemon_move_details["move"] = move_data
-
- # Get Versions and Move Methods associated with each unique move
- pokemon_move_objects = pokemon_moves.filter(move_id=id["move_id"])
- serializer = PokemonMoveSerializer(pokemon_move_objects, many=True, context=self.context)
- pokemon_move_details["version_group_details"] = []
-
- for move in serializer.data:
- version_detail = OrderedDict()
-
- version_detail["level_learned_at"] = move["level"]
- version_detail["version_group"] = version_data[move["version_group"]]
- version_detail["move_learn_method"] = method_data[move["move_learn_method"]]
- version_detail["order"] = move["order"]
-
- pokemon_move_details["version_group_details"].append(version_detail)
-
- move_list.append(pokemon_move_details)
-
- return move_list
-
- # {
- # "item": {
- # "name": "soft-sand",
- # "url": "https://pokeapi.co/api/v2/item/214/"
- # },
- # "version_details": [
- # {
- # "rarity": 5,
- # "version": {
- # "name": "diamond",
- # "url": "https://pokeapi.co/api/v2/version/12/"
- # }
- # },
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["item", "version_details"],
- "properties": {
- "item": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["soft-sand"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/item/214/"],
- },
- },
- },
- "version_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["rarity", "version"],
- "properties": {
- "rarity": {
- "type": "integer",
- "format": "int32",
- "examples": [5],
- },
- "version": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["diamond"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version/12/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_held_items(self, obj):
- # Get items related to this pokemon and pull out unique Item IDs
- pokemon_items = PokemonItem.objects.filter(pokemon_id=obj).order_by("item_id")
- item_ids = pokemon_items.values("item_id").distinct()
- item_list = []
-
- for id in item_ids:
- pokemon_item_details = OrderedDict()
-
- # Get each Unique Item by ID
- item_object = Item.objects.get(pk=id["item_id"])
- item_data = ItemSummarySerializer(item_object, context=self.context).data
- pokemon_item_details["item"] = item_data
-
- # Get Versions associated with each unique item
- pokemon_item_objects = pokemon_items.filter(item_id=id["item_id"])
- serializer = PokemonItemSerializer(pokemon_item_objects, many=True, context=self.context)
- pokemon_item_details["version_details"] = []
-
- for item in serializer.data:
- version_detail = OrderedDict()
-
- version_detail["rarity"] = item["rarity"]
- version_detail["version"] = item["version"]
-
- pokemon_item_details["version_details"].append(version_detail)
-
- item_list.append(pokemon_item_details)
-
- return item_list
-
- # {
- # "ability": {
- # "name": "sand-veil",
- # "url": "https://pokeapi.co/api/v2/ability/8/"
- # },
- # "is_hidden": false,
- # "slot": 1
- # },
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["ability", "is_hidden", "slot"],
- "properties": {
- "ability": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["sand-veil"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/ability/8/"],
- },
- },
- },
- "is_hidden": {"type": "boolean"},
- "slot": {"type": "integer", "format": "int32", "examples": [1]},
- },
- },
- }
- )
- def get_pokemon_abilities(self, obj):
- pokemon_ability_objects = PokemonAbility.objects.filter(pokemon=obj)
- data = PokemonAbilitySerializer(pokemon_ability_objects, many=True, context=self.context).data
- abilities = []
-
- for ability in data:
- del ability["pokemon"]
- abilities.append(ability)
-
- return abilities
-
- # {
- # "abilities": [
- # {
- # "ability": {
- # "name": "levitate",
- # "url": "https://pokeapi.co/api/v2/ability/26/"
- # },
- # "is_hidden": false,
- # "slot": 1
- # }
- # ],
- # "generation": {
- # "name": "generation-vi",
- # "url": "https://pokeapi.co/api/v2/generation/6/"
- # }
- # }
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["abilities", "generation"],
- "properties": {
- "abilities": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["ability", "is_hidden", "slot"],
- "properties": {
- "ability": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["levitate"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/ability/26/"],
- },
- },
- },
- "is_hidden": {"type": "boolean"},
- "slot": {
- "type": "integer",
- "format": "int32",
- "examples": [1],
- },
- },
- },
- },
- "generation": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["generation-vi"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/generation/6/"],
- },
- },
- },
- },
- },
- }
- )
- def get_past_pokemon_abilities(self, obj):
- pokemon_past_ability_objects = PokemonAbilityPast.objects.filter(pokemon=obj)
- pokemon_past_abilities = PokemonAbilityPastSerializer(
- pokemon_past_ability_objects, many=True, context=self.context
- ).data
-
- # post-process to the form we want
- current_generation = ""
- past_obj = {}
- final_data = []
- for pokemon_past_ability in pokemon_past_abilities:
- del pokemon_past_ability["pokemon"]
-
- generation = pokemon_past_ability["generation"]["name"]
- if generation != current_generation:
- current_generation = generation
- past_obj = {}
-
- # create past abilities object for this generation
- past_obj["generation"] = pokemon_past_ability["generation"]
- del pokemon_past_ability["generation"]
-
- # create abilities array
- past_obj["abilities"] = [pokemon_past_ability]
-
- # add to past abilities array
- final_data.append(past_obj)
-
- else:
- # add to existing array for this generation
- del pokemon_past_ability["generation"]
- past_obj["abilities"].append(pokemon_past_ability)
+ return list(items_grouped.values())
- return final_data
+ @extend_schema_field(PokemonAbilitySerializer(many=True))
+ def get_pokemon_abilities(self, obj: Pokemon) -> ReturnList[ReturnDict[str, Any]]:
+ abilities = PokemonAbility.objects.filter(pokemon=obj).select_related("ability")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonAbilitySerializer(abilities, many=True, context=self.context).data,
+ )
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["generation", "stats"],
- "properties": {
- "generation": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["generation-vi"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/generation/6/"],
- },
- },
- },
- "stats": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["base_stat", "effort", "stat"],
- "properties": {
- "base_stat": {
- "type": "integer",
- "format": "int32",
- "examples": [45],
- },
- "effort": {
- "type": "integer",
- "format": "int32",
- "examples": [0],
- },
- "stat": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["speed"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/stat/6/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_past_pokemon_stats(self, obj):
- pokemon_past_stat_objects = PokemonStatPast.objects.filter(pokemon=obj)
- pokemon_past_stats = PokemonStatPastSerializer(pokemon_past_stat_objects, many=True, context=self.context).data
-
- # post-process to the form we want
- current_generation = ""
- past_obj = {}
- final_data = []
- for pokemon_past_stat in pokemon_past_stats:
- generation = pokemon_past_stat["generation"]["name"]
- if generation != current_generation:
- current_generation = generation
- past_obj = {}
-
- # create past stats object for this generation
- past_obj["generation"] = pokemon_past_stat["generation"]
- del pokemon_past_stat["generation"]
-
- # create stats array
- past_obj["stats"] = [pokemon_past_stat]
-
- # add to past stats array
- final_data.append(past_obj)
-
- else:
- # add to existing array for this generation
- del pokemon_past_stat["generation"]
- past_obj["stats"].append(pokemon_past_stat)
+ @extend_schema_field(PokemonPastAbilitySerializer(many=True))
+ def get_past_pokemon_abilities(self, obj: Pokemon) -> list[dict[str, Any]]:
+ past_abilities = (
+ PokemonAbilityPast.objects.filter(pokemon=obj, generation__isnull=False)
+ .select_related("generation", "ability")
+ .order_by("generation_id")
+ )
- return final_data
+ final_data: list[dict[str, Any]] = []
+ for _, group in itertools.groupby(past_abilities, key=lambda x: cast("Generation", x.generation).name):
+ group_list: Sequence[PokemonAbilityPast] = list(group)
+ gen_data = cast(
+ "ReturnDict[str, Any]",
+ GenerationSummarySerializer(group_list[0].generation, context=self.context).data,
+ )
+ abilities_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonAbilityPastSerializer(group_list, many=True, context=self.context).data,
+ )
+ final_data.append(
+ {
+ "generation": gen_data,
+ "abilities": abilities_data,
+ }
+ )
- # {
- # "slot": 1,
- # "type": {
- # "name": "ghost",
- # "url": "https://pokeapi.co/api/v2/type/8/"
- # }
- # },
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["slot", "type"],
- "properties": {
- "slot": {"type": "integer", "format": "int32", "examples": [1]},
- "type": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["ghost"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/8/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_types(self, obj):
- poke_type_objects = PokemonType.objects.filter(pokemon=obj)
- poke_types = PokemonTypeSerializer(poke_type_objects, many=True, context=self.context).data
-
- for poke_type in poke_types:
- del poke_type["pokemon"]
-
- return poke_types
-
- # "past_types": [
- # {
- # "generation": {
- # "name": "generation-v",
- # "url": "https://pokeapi.co/api/v2/generation/5/"
- # },
- # "types": [
- # {
- # "slot": 1,
- # "type": {
- # "name": "normal",
- # "url": "https://pokeapi.co/api/v2/type/1/"
- # }
- # }
- # ]
- # }
- # ],
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["generation", "types"],
- "properties": {
- "generation": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["generation-v"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/generation/5/"],
- },
- },
- },
- "types": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["slot", "type"],
- "properties": {
- "slot": {
- "type": "integer",
- "format": "int32",
- "examples": [1],
- },
- "type": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": ["normal"],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/type/1/"],
- },
- },
- },
- },
- },
- },
- },
- },
- }
- )
- def get_past_pokemon_types(self, obj):
- poke_past_type_objects = PokemonTypePast.objects.filter(pokemon=obj)
- poke_past_types = PokemonTypePastSerializer(poke_past_type_objects, many=True, context=self.context).data
+ return final_data
- # post-process to the form we want
- current_generation = ""
- past_obj = {}
- final_data = []
- for poke_past_type in poke_past_types:
- del poke_past_type["pokemon"]
+ @extend_schema_field(PokemonPastStatSerializer(many=True))
+ def get_past_pokemon_stats(self, obj: Pokemon) -> list[dict[str, Any]]:
+ past_stats = (
+ PokemonStatPast.objects.filter(pokemon=obj, generation__isnull=False)
+ .select_related("generation", "stat")
+ .order_by("generation_id")
+ )
- generation = poke_past_type["generation"]["name"]
- if generation != current_generation:
- current_generation = generation
- past_obj = {}
+ final_data: list[dict[str, Any]] = []
+ for _, group in itertools.groupby(past_stats, key=lambda x: cast("Generation", x.generation).name):
+ group_list: Sequence[PokemonStatPast] = list(group)
+ gen_data = cast(
+ "ReturnDict[str, Any]",
+ GenerationSummarySerializer(group_list[0].generation, context=self.context).data,
+ )
+ stats_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonStatPastSerializer(group_list, many=True, context=self.context).data,
+ )
+ final_data.append(
+ {
+ "generation": gen_data,
+ "stats": stats_data,
+ }
+ )
- # create past types object for this generation
- past_obj["generation"] = poke_past_type["generation"]
- del poke_past_type["generation"]
+ return final_data
- # create types array
- past_obj["types"] = [poke_past_type]
+ @extend_schema_field(PokemonTypeSerializer(many=True))
+ def get_pokemon_types(self, obj: Pokemon) -> ReturnList[ReturnDict[str, Any]]:
+ types = PokemonType.objects.filter(pokemon=obj).select_related("type").order_by("slot")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonTypeSerializer(types, many=True, context=self.context).data,
+ )
- # add to past types array
- final_data.append(past_obj)
+ @extend_schema_field(PokemonPastTypeSerializer(many=True))
+ def get_past_pokemon_types(self, obj: Pokemon) -> list[dict[str, Any]]:
+ past_types = (
+ PokemonTypePast.objects.filter(pokemon=obj, generation__isnull=False)
+ .select_related("generation", "type")
+ .order_by("generation_id", "slot")
+ )
- else:
- # add to existing array for this generation
- del poke_past_type["generation"]
- past_obj["types"].append(poke_past_type)
+ final_data: list[dict[str, Any]] = []
+ for _, group in itertools.groupby(past_types, key=lambda x: cast("Generation", x.generation).name):
+ group_list: Sequence[PokemonTypePast] = list(group)
+ gen_data = cast(
+ "ReturnDict[str, Any]", GenerationSummarySerializer(group_list[0].generation, context=self.context).data
+ )
+ types_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonTypePastSerializer(group_list, many=True, context=self.context).data,
+ )
+ final_data.append(
+ {
+ "generation": gen_data,
+ "types": types_data,
+ }
+ )
return final_data
- @extend_schema_field(
- field={
- "type": "string",
- "examples": ["https://pokeapi.co/api/v2/pokemon/1/encounters"],
- }
- )
- def get_encounters(self, obj):
- return reverse("pokemon_encounters", kwargs={"pokemon_id": obj.pk})
+ @extend_schema_field(serializers.CharField)
+ def get_encounters(self, obj: Pokemon) -> str:
+ return reverse("pokemon_encounters", kwargs={"pokemon_id": obj.pk}, request=self.context.get("request"))
#################################
# POKEMON SPECIES SERIALIZERS #
#################################
-class EvolutionTriggerNameSerializer(serializers.HyperlinkedModelSerializer):
+
+
+class EvolutionTriggerNameSerializer(serializers.HyperlinkedModelSerializer[EvolutionTriggerName]):
language = LanguageSummarySerializer()
class Meta:
@@ -5176,7 +3137,7 @@ class Meta:
fields = ("name", "language")
-class EvolutionTriggerDetailSerializer(serializers.HyperlinkedModelSerializer):
+class EvolutionTriggerDetailSerializer(serializers.HyperlinkedModelSerializer[EvolutionTrigger]):
names = EvolutionTriggerNameSerializer(many=True, read_only=True, source="evolutiontriggername")
pokemon_species = serializers.SerializerMethodField("get_species")
@@ -5184,38 +3145,16 @@ class Meta:
model = EvolutionTrigger
fields = ("id", "name", "names", "pokemon_species")
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["ivysaur"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/2/"],
- },
- },
- },
- }
- )
- def get_species(self, obj):
- evo_objects = PokemonEvolution.objects.filter(evolution_trigger=obj)
- species_list = []
- species_names = set()
-
- for evo in evo_objects:
- species = PokemonSpeciesSummarySerializer(evo.evolved_species, context=self.context).data
- if species["name"] not in species_names:
- species_list.append(species)
- species_names.add(species["name"])
-
- return species_list
+ @extend_schema_field(PokemonSpeciesSummarySerializer(many=True))
+ def get_species(self, obj: EvolutionTrigger) -> ReturnList[ReturnDict[str, Any]]:
+ species = PokemonSpecies.objects.filter(evolved_species__evolution_trigger=obj).distinct().order_by("id")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesSummarySerializer(species, many=True, context=self.context).data,
+ )
-class PokemonSpeciesDescriptionSerializer(serializers.ModelSerializer):
+class PokemonSpeciesDescriptionSerializer(serializers.ModelSerializer[PokemonSpeciesDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -5223,7 +3162,7 @@ class Meta:
fields = ("description", "language")
-class PokemonSpeciesFlavorTextSerializer(serializers.ModelSerializer):
+class PokemonSpeciesFlavorTextSerializer(serializers.ModelSerializer[PokemonSpeciesFlavorText]):
flavor_text = serializers.CharField()
language = LanguageSummarySerializer()
version = VersionSummarySerializer()
@@ -5233,15 +3172,7 @@ class Meta:
fields = ("flavor_text", "language", "version")
-class PokemonSpeciesNameSerializer(serializers.ModelSerializer):
- language = LanguageSummarySerializer()
-
- class Meta:
- model = PokemonSpeciesName
- fields = ("name", "genus", "language")
-
-
-class PokemonSpeciesEvolutionSerializer(serializers.ModelSerializer):
+class PokemonSpeciesEvolutionSerializer(serializers.ModelSerializer[PokemonSpecies]):
"""
This is here purely to help build pokemon evolution chains
"""
@@ -5251,7 +3182,7 @@ class Meta:
fields = ("name", "id", "evolves_from_species", "is_baby")
-class PokemonSpeciesDetailSerializer(serializers.ModelSerializer):
+class PokemonSpeciesDetailSerializer(serializers.ModelSerializer[PokemonSpecies]):
names = serializers.SerializerMethodField("get_pokemon_names")
form_descriptions = PokemonSpeciesDescriptionSerializer(
many=True, read_only=True, source="pokemonspeciesdescription"
@@ -5304,192 +3235,59 @@ class Meta:
"varieties",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["language", "name"],
- "properties": {
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- "name": {"type": "string", "examples": ["bulbasaur"]},
- },
- },
- }
- )
- def get_pokemon_names(self, obj):
- species_results = PokemonSpeciesName.objects.filter(pokemon_species=obj)
- species_serializer = PokemonSpeciesNameSerializer(species_results, many=True, context=self.context)
-
- data = species_serializer.data
-
- for name in data:
- del name["genus"]
-
- return data
+ @extend_schema_field(PokemonSpeciesNameSerializer(many=True))
+ def get_pokemon_names(self, obj: PokemonSpecies) -> ReturnList[ReturnDict[str, Any]]:
+ species_results = PokemonSpeciesName.objects.filter(pokemon_species=obj).select_related("language")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesNameSerializer(species_results, many=True, context=self.context).data,
+ )
- # {
- # "genus": "Seed Pokémon",
- # "language": {
- # "name": "en",
- # "url": "https://pokeapi.co/api/v2/language/9/"
- # }
- # },
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["genus", "language"],
- "properties": {
- "genus": {"type": "string", "examples": ["Seed Pokémon"]},
- "language": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["en"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/language/9/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_genera(self, obj):
- results = PokemonSpeciesName.objects.filter(pokemon_species=obj)
- serializer = PokemonSpeciesNameSerializer(results, many=True, context=self.context)
- data = serializer.data
- genera = []
-
- for entry in data:
- if entry["genus"]:
- del entry["name"]
- genera.append(entry)
-
- return genera
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["monster"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/egg-group/1/"],
- },
- },
- },
- }
- )
- def get_pokemon_egg_groups(self, obj):
- results = PokemonEggGroup.objects.filter(pokemon_species=obj)
- data = PokemonEggGroupSerializer(results, many=True, context=self.context).data
- groups = []
- for group in data:
- groups.append(group["egg_group"])
-
- return groups
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["is_default", "pokemon"],
- "properties": {
- "is_default": {"type": "boolean"},
- "pokemon": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bulbasaur"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon/1/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokemon_varieties(self, obj):
- results = Pokemon.objects.filter(pokemon_species=obj)
- summary_data = PokemonSummarySerializer(results, many=True, context=self.context).data
- detail_data = PokemonDetailSerializer(results, many=True, context=self.context).data
-
- varieties = []
-
- for index, pokemon in enumerate(detail_data):
- entry = OrderedDict()
- entry["is_default"] = pokemon["is_default"]
- entry["pokemon"] = summary_data[index]
- varieties.append(entry)
-
- return varieties
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["area", "base_score", "rate"],
- "properties": {
- "area": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["field"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pal-park-area/2/"],
- },
- },
- },
- "base_score": {
- "type": "integer",
- "format": "int32",
- "examples": [50],
- },
- "rate": {"type": "integer", "format": "int32", "examples": [30]},
- },
- },
- }
- )
- def get_encounters(self, obj):
- pal_park_objects = PalPark.objects.filter(pokemon_species=obj)
- parks = PalParkSerializer(pal_park_objects, many=True, context=self.context).data
- encounters = []
+ @extend_schema_field(PokemonSpeciesGenusSerializer(many=True))
+ def get_pokemon_genera(self, obj: PokemonSpecies) -> ReturnList[ReturnDict[str, Any]]:
+ results = (
+ PokemonSpeciesName.objects.filter(pokemon_species=obj, genus__isnull=False)
+ .exclude(genus="")
+ .select_related("language")
+ )
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesGenusSerializer(results, many=True, context=self.context).data,
+ )
- for encounter in parks:
- del encounter["pokemon_species"]
- encounters.append(encounter)
+ @extend_schema_field(EggGroupSummarySerializer(many=True))
+ def get_pokemon_egg_groups(self, obj: PokemonSpecies) -> ReturnList[ReturnDict[str, Any]]:
+ egg_groups = EggGroup.objects.filter(pokemonegggroup__pokemon_species=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ EggGroupSummarySerializer(egg_groups, many=True, context=self.context).data,
+ )
- return encounters
+ @extend_schema_field(PokemonSpeciesVarietySerializer(many=True))
+ def get_pokemon_varieties(self, obj: PokemonSpecies) -> list[dict[str, Any]]:
+ pokemon_list = Pokemon.objects.filter(pokemon_species=obj)
+ summaries = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSummarySerializer(pokemon_list, many=True, context=self.context).data,
+ )
+ return [
+ {
+ "is_default": pk.is_default,
+ "pokemon": summary,
+ }
+ for pk, summary in zip(pokemon_list, summaries, strict=True)
+ ]
+
+ @extend_schema_field(PokemonSpeciesPalParkEncounterSerializer(many=True))
+ def get_encounters(self, obj: PokemonSpecies) -> ReturnList[ReturnDict[str, Any]]:
+ pal_park_objects = PalPark.objects.filter(pokemon_species=obj).select_related("pal_park_area")
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesPalParkEncounterSerializer(pal_park_objects, many=True, context=self.context).data,
+ )
-class PokemonEvolutionSerializer(serializers.ModelSerializer):
+class PokemonEvolutionSerializer(serializers.ModelSerializer[PokemonEvolution]):
version_group = VersionGroupSummarySerializer()
item = ItemSummarySerializer(source="evolution_item")
held_item = ItemSummarySerializer()
@@ -5540,349 +3338,69 @@ class Meta:
)
-class EvolutionChainDetailSerializer(serializers.ModelSerializer):
+class EvolutionChainLinkSerializer(serializers.Serializer[Any]):
+ is_baby = serializers.BooleanField()
+ species = PokemonSpeciesSummarySerializer()
+ evolution_details = PokemonEvolutionSerializer(many=True)
+ evolves_to = serializers.ListField(child=serializers.DictField())
+
+
+class EvolutionChainDetailSerializer(serializers.ModelSerializer[EvolutionChain]):
baby_trigger_item = ItemSummarySerializer()
chain = serializers.SerializerMethodField("build_chain")
+ POKEMON_EVOLUTION_FK_FIELDS: ClassVar[list[str]] = [
+ field.name
+ for field in PokemonEvolution._meta.get_fields()
+ if field.is_relation and (field.many_to_one or field.one_to_one)
+ ]
+
class Meta:
model = EvolutionChain
fields = ("id", "baby_trigger_item", "chain")
- # TODO: Revisit Schema
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["evolution_details", "evolves_to", "is_baby", "species"],
- "properties": {
- "evolution_details": {"type": "array", "items": {}, "examples": []},
- "evolves_to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [
- "evolution_details",
- "evolves_to",
- "is_baby",
- "species",
- ],
- "properties": {
- "evolution_details": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [
- "version_group",
- "is_default",
- "gender",
- "held_item",
- "item",
- "known_move",
- "known_move_type",
- "location",
- "min_affection",
- "min_beauty",
- "min_damage_taken",
- "min_happiness",
- "min_level",
- "min_move_count",
- "min_steps",
- "near_special_rock",
- "needs_multiplayer",
- "needs_overworld_rain",
- "party_species",
- "party_type",
- "relative_physical_stats",
- "time_of_day",
- "trade_species",
- "trigger",
- "turn_upside_down",
- "used_move",
- "region",
- "base_form",
- "evolved_form",
- ],
- "properties": {
- "version_group": {
- "type": "object",
- "nullable": False,
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": [1],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": [2],
- },
- },
- },
- "is_default": {"type": "boolean"},
- "gender": {
- "type": "",
- "nullable": True,
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": [1],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": [2],
- },
- },
- },
- "held_item": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": [1],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": [2],
- },
- },
- },
- "item": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- "examples": [1],
- },
- "url": {
- "type": "string",
- "format": "uri",
- "examples": [2],
- },
- },
- },
- "known_move": {
- "type": "",
- "nullable": True,
- },
- "known_move_type": {
- "type": "",
- "nullable": True,
- },
- "location": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- },
- "url": {
- "type": "string",
- "format": "uri",
- },
- },
- },
- "min_affection": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_beauty": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_damage_taken": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_happiness": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_level": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_move_count": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "min_steps": {
- "type": "integer",
- "format": "int32",
- "nullable": True,
- },
- "near_special_rock": {
- "type": "boolean",
- "nullable": True,
- },
- "needs_multiplayer": {
- "type": "boolean",
- "nullable": True,
- },
- "needs_overworld_rain": {
- "type": "boolean",
- "nullable": True,
- },
- "party_species": {
- "type": "string",
- "nullable": True,
- },
- "party_type": {
- "type": "string",
- "nullable": True,
- },
- "relative_physical_stats": {
- "type": "string",
- "nullable": True,
- },
- "time_of_day": {"type": "string"},
- "trade_species": {
- "type": "string",
- "nullable": True,
- },
- "trigger": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {
- "type": "string",
- },
- "url": {
- "type": "string",
- "format": "uri",
- },
- },
- },
- "turn_upside_down": {"type": "boolean"},
- "used_move": {
- "type": "",
- "nullable": True,
- },
- "region": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string"},
- "url": {
- "type": "string",
- "format": "uri",
- },
- },
- },
- "base_form": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string"},
- "url": {
- "type": "string",
- "format": "uri",
- },
- },
- },
- "evolved_form": {
- "type": "object",
- "nullable": True,
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string"},
- "url": {
- "type": "string",
- "format": "uri",
- },
- },
- },
- },
- },
- },
- "is_baby": {"type": "boolean"},
- "species": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["happiny"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/440/"],
- },
- },
- },
- },
- },
- },
- "is_baby": {"type": "boolean"},
- "species": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["happiny"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/440/"],
- },
- },
- },
- },
- }
- )
- def build_chain(self, obj):
- chain_id = obj.id
-
- pokemon_objects = PokemonSpecies.objects.filter(evolution_chain_id=chain_id).order_by("order")
- summary_data = PokemonSpeciesSummarySerializer(pokemon_objects, many=True, context=self.context).data
- ref_data = PokemonSpeciesEvolutionSerializer(pokemon_objects, many=True, context=self.context).data
+ @extend_schema_field(EvolutionChainLinkSerializer)
+ def build_chain(self, obj: EvolutionChain) -> dict[str, Any]:
+ pokemon_objects = PokemonSpecies.objects.filter(evolution_chain=obj).order_by("order")
+ summary_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesSummarySerializer(pokemon_objects, many=True, context=self.context).data,
+ )
+ ref_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonSpeciesEvolutionSerializer(pokemon_objects, many=True, context=self.context).data,
+ )
- # convert evolution data list to tree
evolution_tree = self.build_evolution_tree(ref_data)
-
- # serialize chain recursively from tree
- chain = self.build_chain_link_entry(evolution_tree, summary_data)
-
- return chain
+ return self.build_chain_link_entry(evolution_tree, summary_data)
# converts a list of Pokemon species evolution data into a tree representing the evolution chain
- def build_evolution_tree(self, species_evolution_data):
- evolution_tree = OrderedDict()
- evolution_tree["species"] = species_evolution_data[0]
- evolution_tree["children"] = []
+ def build_evolution_tree(self, species_evolution_data: ReturnList[ReturnDict[str, Any]]) -> dict[str, Any]:
+ if not species_evolution_data:
+ return {}
+
+ first_species: dict[str, Any] = species_evolution_data[0]
+ evolution_tree: dict[str, Any] = {"species": first_species, "children": []}
for species in species_evolution_data[1:]:
- chain_link = OrderedDict()
- chain_link["species"] = species
- chain_link["children"] = []
+ species_item: dict[str, Any] = species
+ chain_link: dict[str, Any] = {
+ "species": species_item,
+ "children": [],
+ }
- evolves_from_species_id = chain_link["species"]["evolves_from_species"]
+ species_dict: dict[str, Any] = chain_link["species"]
+ evolves_from_species_id = species_dict["evolves_from_species"]
- # find parent link by DFS
parent_link = evolution_tree
search_stack = [parent_link]
- while len(search_stack) > 0:
- l = search_stack.pop()
- if l["species"]["id"] == evolves_from_species_id:
- parent_link = l
+ while search_stack:
+ link = search_stack.pop()
+ if link["species"]["id"] == evolves_from_species_id:
+ parent_link = link
break
-
- # "left" to "right" requires reversing the list of children
- search_stack += reversed(l["children"])
+ search_stack.extend(reversed(link["children"]))
parent_link["children"].append(chain_link)
@@ -5890,50 +3408,56 @@ def build_evolution_tree(self, species_evolution_data):
# serializes an evolution chain link recursively
# chain_link is a tree representing an evolution chain
- def build_chain_link_entry(self, chain_link, summary_data):
- entry = OrderedDict()
+ def build_chain_link_entry(
+ self, chain_link: dict[str, Any], summary_data: ReturnList[ReturnDict[str, Any]]
+ ) -> dict[str, Any]:
+ species = chain_link["species"]
evolution_data = None
- species = chain_link["species"]
if species["evolves_from_species"]:
- evolution_object = PokemonEvolution.objects.filter(evolved_species=species["id"])
+ evolution_objects = PokemonEvolution.objects.filter(evolved_species=species["id"]).select_related(
+ *self.POKEMON_EVOLUTION_FK_FIELDS
+ )
+ evolution_data = cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonEvolutionSerializer(evolution_objects, many=True, context=self.context).data,
+ )
- evolution_data = PokemonEvolutionSerializer(evolution_object, many=True, context=self.context).data
+ return {
+ "is_baby": species["is_baby"],
+ "species": next(x for x in summary_data if x["name"] == species["name"]),
+ "evolution_details": evolution_data or [],
+ "evolves_to": [self.build_chain_link_entry(c, summary_data) for c in chain_link["children"]],
+ }
- entry["is_baby"] = species["is_baby"]
- species_summary = next(x for x in summary_data if x["name"] == species["name"])
- entry["species"] = species_summary
+############################
+# POKEATHLON SERIALIZERS #
+############################
- entry["evolution_details"] = evolution_data or []
- evolves_to = [self.build_chain_link_entry(c, summary_data) for c in chain_link["children"]]
- entry["evolves_to"] = evolves_to
+class PokeathlonStatNameSerializer(serializers.HyperlinkedModelSerializer[PokeathlonStatName]):
+ language = LanguageSummarySerializer()
- return entry
+ class Meta:
+ model = PokeathlonStatName
+ fields = ("name", "language")
-class PokemonDexNumberSerializer(serializers.ModelSerializer):
- entry_number = serializers.IntegerField(source="pokedex_number")
- pokemon_species = PokemonSpeciesSummarySerializer()
+class PokeathlonStatAffectingNatureSerializer(serializers.ModelSerializer[NaturePokeathlonStat]):
+ nature = NatureSummarySerializer()
class Meta:
- model = PokemonDexNumber
- fields = ("pokedex", "entry_number", "pokemon_species")
-
+ model = NaturePokeathlonStat
+ fields = ("max_change", "nature")
-############################
-# POKEATHLON SERIALIZERS #
-############################
-class PokeathlonStatNameSerializer(serializers.HyperlinkedModelSerializer):
- language = LanguageSummarySerializer()
- class Meta:
- model = PokeathlonStatName
- fields = ("name", "language")
+class PokeathlonStatAffectingNaturesSerializer(serializers.Serializer[Any]):
+ increase = PokeathlonStatAffectingNatureSerializer(many=True)
+ decrease = PokeathlonStatAffectingNatureSerializer(many=True)
-class PokeathlonStatDetailSerializer(serializers.HyperlinkedModelSerializer):
+class PokeathlonStatDetailSerializer(serializers.HyperlinkedModelSerializer[PokeathlonStat]):
names = PokeathlonStatNameSerializer(many=True, read_only=True, source="pokeathlonstatname")
affecting_natures = serializers.SerializerMethodField("get_natures_that_affect")
@@ -5941,87 +3465,29 @@ class Meta:
model = PokeathlonStat
fields = ("id", "name", "affecting_natures", "names")
- @extend_schema_field(
- field={
- "type": "object",
- "required": ["decrease", "increase"],
- "properties": {
- "decrease": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["max_change", "nature"],
- "properties": {
- "max_change": {
- "type": "integer",
- "format": "int32",
- "maximum": -1,
- "examples": [-1],
- },
- "nature": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["hardy"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/nature/1/"],
- },
- },
- },
- },
- },
- },
- "increase": {
- "type": "array",
- "items": {
- "type": "object",
- "required": ["max_change", "nature"],
- "properties": {
- "max_change": {
- "type": "integer",
- "format": "int32",
- "minimum": 1,
- "examples": [2],
- },
- "nature": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["hardy"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/nature/1/"],
- },
- },
- },
- },
- },
- },
- },
+ @extend_schema_field(PokeathlonStatAffectingNaturesSerializer)
+ def get_natures_that_affect(self, obj: PokeathlonStat) -> dict[str, ReturnList[ReturnDict[str, Any]]]:
+ base_qs = NaturePokeathlonStat.objects.filter(pokeathlon_stat=obj).select_related("nature")
+ increases = base_qs.filter(max_change__gt=0)
+ decreases = base_qs.filter(max_change__lte=0)
+ return {
+ "increase": cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokeathlonStatAffectingNatureSerializer(increases, many=True, context=self.context).data,
+ ),
+ "decrease": cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokeathlonStatAffectingNatureSerializer(decreases, many=True, context=self.context).data,
+ ),
}
- )
- def get_natures_that_affect(self, obj):
- stat_change_objects = NaturePokeathlonStat.objects.filter(pokeathlon_stat=obj)
- stat_changes = NaturePokeathlonStatSerializer(stat_change_objects, many=True, context=self.context).data
- changes = OrderedDict([("increase", []), ("decrease", [])])
-
- for change in stat_changes:
- del change["pokeathlon_stat"]
- if change["max_change"] > 0:
- changes["increase"].append(change)
- else:
- changes["decrease"].append(change)
-
- return changes
#########################
# POKEDEX SERIALIZERS #
#########################
-class PokedexNameSerializer(serializers.HyperlinkedModelSerializer):
+
+
+class PokedexNameSerializer(serializers.HyperlinkedModelSerializer[PokedexName]):
language = LanguageSummarySerializer()
class Meta:
@@ -6029,7 +3495,7 @@ class Meta:
fields = ("name", "language")
-class PokedexDescriptionSerializer(serializers.HyperlinkedModelSerializer):
+class PokedexDescriptionSerializer(serializers.HyperlinkedModelSerializer[PokedexDescription]):
language = LanguageSummarySerializer()
class Meta:
@@ -6037,7 +3503,16 @@ class Meta:
fields = ("description", "language")
-class PokedexDetailSerializer(serializers.ModelSerializer):
+class PokemonDexNumberSerializer(serializers.ModelSerializer[PokemonDexNumber]):
+ entry_number = serializers.IntegerField(source="pokedex_number")
+ pokemon_species = PokemonSpeciesSummarySerializer()
+
+ class Meta:
+ model = PokemonDexNumber
+ fields = ("entry_number", "pokemon_species")
+
+
+class PokedexDetailSerializer(serializers.ModelSerializer[Pokedex]):
region = RegionSummarySerializer()
names = PokedexNameSerializer(many=True, read_only=True, source="pokedexname")
descriptions = PokedexDescriptionSerializer(many=True, read_only=True, source="pokedexdescription")
@@ -6057,76 +3532,31 @@ class Meta:
"version_groups",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["entry_number", "pokemon_species"],
- "properties": {
- "entry_number": {
- "type": "integer",
- "format": "int32",
- "examples": [1],
- },
- "pokemon_species": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["bulbasaur"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokemon-species/1/"],
- },
- },
- },
- },
- },
- }
- )
- def get_pokedex_entries(self, obj):
- results = PokemonDexNumber.objects.filter(pokedex=obj).order_by("pokedex_number")
- serializer = PokemonDexNumberSerializer(results, many=True, context=self.context)
- data = serializer.data
-
- for entry in data:
- del entry["pokedex"]
-
- return data
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["the-teal-mask"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/version-group/26/"],
- },
- },
- },
- }
- )
- def get_pokedex_version_groups(self, obj):
- dex_group_objects = PokedexVersionGroup.objects.filter(pokedex=obj)
- dex_groups = PokedexVersionGroupSerializer(dex_group_objects, many=True, context=self.context).data
- results = []
-
- for dex_group in dex_groups:
- results.append(dex_group["version_group"])
+ @extend_schema_field(PokemonDexNumberSerializer(many=True))
+ def get_pokedex_entries(self, obj: Pokedex) -> ReturnList[ReturnDict[str, Any]]:
+ entries = (
+ PokemonDexNumber.objects.filter(pokedex=obj).select_related("pokemon_species").order_by("pokedex_number")
+ )
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokemonDexNumberSerializer(entries, many=True, context=self.context).data,
+ )
- return results
+ @extend_schema_field(VersionGroupSummarySerializer(many=True))
+ def get_pokedex_version_groups(self, obj: Pokedex) -> ReturnList[ReturnDict[str, Any]]:
+ version_groups = VersionGroup.objects.filter(pokedexversiongroup__pokedex=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ VersionGroupSummarySerializer(version_groups, many=True, context=self.context).data,
+ )
#########################
# VERSION SERIALIZERS #
#########################
-class VersionNameSerializer(serializers.ModelSerializer):
+
+
+class VersionNameSerializer(serializers.ModelSerializer[VersionName]):
language = LanguageSummarySerializer()
class Meta:
@@ -6134,7 +3564,7 @@ class Meta:
fields = ("name", "language")
-class VersionDetailSerializer(serializers.ModelSerializer):
+class VersionDetailSerializer(serializers.ModelSerializer[Version]):
"""
Should have a link to Version Group info but the Circular
dependency and compilation order fight eachother and I'm
@@ -6149,7 +3579,7 @@ class Meta:
fields = ("id", "name", "names", "version_group")
-class VersionGroupDetailSerializer(serializers.ModelSerializer):
+class VersionGroupDetailSerializer(serializers.ModelSerializer[VersionGroup]):
generation = GenerationSummarySerializer()
versions = VersionSummarySerializer(many=True, read_only=True, source="version")
regions = serializers.SerializerMethodField("get_version_group_regions")
@@ -6169,87 +3599,26 @@ class Meta:
"versions",
)
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["kanto"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/region/1/"],
- },
- },
- },
- }
- )
- def get_version_group_regions(self, obj):
- vg_regions = VersionGroupRegion.objects.filter(version_group=obj)
- data = VersionGroupRegionSerializer(vg_regions, many=True, context=self.context).data
- regions = []
-
- for region in data:
- regions.append(region["region"])
-
- return regions
-
- # "name": "level-up",
- # "url": "https://pokeapi.co/api/v2/move-learn-method/1/"
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["level-up"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/move-learn-method/1/"],
- },
- },
- },
- }
- )
- def get_learn_methods(self, obj):
- learn_method_objects = VersionGroupMoveLearnMethod.objects.filter(version_group=obj)
- learn_method_data = VersionGroupMoveLearnMethodSerializer(
- learn_method_objects, many=True, context=self.context
- ).data
- methods = []
-
- for method in learn_method_data:
- methods.append(method["move_learn_method"])
-
- return methods
-
- @extend_schema_field(
- field={
- "type": "array",
- "items": {
- "type": "object",
- "required": ["name", "url"],
- "properties": {
- "name": {"type": "string", "examples": ["kanto"]},
- "url": {
- "type": "string",
- "format": "uri",
- "examples": ["https://pokeapi.co/api/v2/pokedex/2/"],
- },
- },
- },
- }
- )
- def get_version_groups_pokedexes(self, obj):
- dex_group_objects = PokedexVersionGroup.objects.filter(version_group=obj)
- dex_groups = PokedexVersionGroupSerializer(dex_group_objects, many=True, context=self.context).data
- results = []
+ @extend_schema_field(RegionSummarySerializer(many=True))
+ def get_version_group_regions(self, obj: VersionGroup) -> ReturnList[ReturnDict[str, Any]]:
+ regions = Region.objects.filter(versiongroupregion__version_group=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ RegionSummarySerializer(regions, many=True, context=self.context).data,
+ )
- for dex_group in dex_groups:
- results.append(dex_group["pokedex"])
+ @extend_schema_field(MoveLearnMethodSummarySerializer(many=True))
+ def get_learn_methods(self, obj: VersionGroup) -> ReturnList[ReturnDict[str, Any]]:
+ methods = MoveLearnMethod.objects.filter(versiongroupmovelearnmethod__version_group=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ MoveLearnMethodSummarySerializer(methods, many=True, context=self.context).data,
+ )
- return results
+ @extend_schema_field(PokedexSummarySerializer(many=True))
+ def get_version_groups_pokedexes(self, obj: VersionGroup) -> ReturnList[ReturnDict[str, Any]]:
+ pokedexes = Pokedex.objects.filter(pokedexversiongroup__version_group=obj).distinct()
+ return cast(
+ "ReturnList[ReturnDict[str, Any]]",
+ PokedexSummarySerializer(pokedexes, many=True, context=self.context).data,
+ )
diff --git a/pokemon_v2/test_models.py b/pokemon_v2/test_models.py
index 9c78683b6..a6532ee1f 100644
--- a/pokemon_v2/test_models.py
+++ b/pokemon_v2/test_models.py
@@ -1,18 +1,23 @@
import csv
import os
import re
+
from django.conf import settings
from django.test import TestCase
+from typing_extensions import override
+
from pokemon_v2.models import *
class AbilityTestCase(TestCase):
+ @override
def setUp(self):
Ability.objects.create(name="Smell", generation_id=3, is_main_series=True)
def fields_are_valid(self):
smell = Ability.objects.get(name="Smell")
- self.assertEqual(smell.generation_id, 3)
+ assert smell.generation is not None
+ self.assertEqual(smell.generation.pk, 3)
class EncounterPokemonDetailTestCase(TestCase):
@@ -70,10 +75,10 @@ def test_all_csv_identifiers_are_ascii_slugs(self):
csv_path = os.path.join(csv_dir, filename)
try:
- with open(csv_path, "r", encoding="utf-8") as csvfile:
+ with open(csv_path, encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile)
- if "identifier" not in reader.fieldnames:
+ if "identifier" not in (reader.fieldnames or []):
continue
for row_num, row in enumerate(reader, start=2):
@@ -94,13 +99,13 @@ def test_all_csv_identifiers_are_ascii_slugs(self):
}
)
- except Exception as e:
+ except Exception as e: # noqa: BLE001
violations.append(
{
"file": filename,
"row": "N/A",
"id": "N/A",
- "identifier": f"Error reading file: {str(e)}",
+ "identifier": f"Error reading file: {e!s}",
}
)
@@ -108,22 +113,27 @@ def test_all_csv_identifiers_are_ascii_slugs(self):
# Report violations
if violations:
- error_lines.append(
- "\n\nFound {} resource(s) with invalid identifiers (not ASCII slugs):".format(len(violations))
+ error_lines.extend(
+ (
+ f"\n\nFound {len(violations)} resource(s) with invalid identifiers (not ASCII slugs):",
+ "\nIdentifiers must match pattern: ^[a-z0-9-]+$",
+ "\nInvalid identifiers found in CSV files:",
+ )
+ )
+
+ error_lines.extend(" - {file} (row {row}, id={id}): {identifier}".format(**v) for v in violations)
+
+ error_lines.extend(
+ (
+ "\nThese identifiers contain invalid characters and must be normalized.",
+ "Update the CSV files in data/v2/csv/ to fix these identifiers.",
+ "\nSuggested fixes:",
+ " - Remove Unicode apostrophes (') and replace with regular hyphens or remove",
+ " - Remove Unicode letters (ñ → n)",
+ " - Remove parentheses and other special characters",
+ " - Convert to lowercase",
+ )
)
- error_lines.append("\nIdentifiers must match pattern: ^[a-z0-9-]+$")
- error_lines.append("\nInvalid identifiers found in CSV files:")
-
- for v in violations:
- error_lines.append(" - {file} (row {row}, id={id}): {identifier}".format(**v))
-
- error_lines.append("\nThese identifiers contain invalid characters and must be normalized.")
- error_lines.append("Update the CSV files in data/v2/csv/ to fix these identifiers.")
- error_lines.append("\nSuggested fixes:")
- error_lines.append(" - Remove Unicode apostrophes (') and replace with regular hyphens or remove")
- error_lines.append(" - Remove Unicode letters (ñ → n)")
- error_lines.append(" - Remove parentheses and other special characters")
- error_lines.append(" - Convert to lowercase")
self.fail("\n".join(error_lines))
diff --git a/pokemon_v2/tests.py b/pokemon_v2/tests.py
index 753403417..9b525a341 100644
--- a/pokemon_v2/tests.py
+++ b/pokemon_v2/tests.py
@@ -1,10 +1,10 @@
import json
-from datetime import datetime
+from datetime import datetime, timezone
+
from rest_framework import status
from rest_framework.test import APITestCase
-from pokemon_v2.models import *
-# pylint: disable=redefined-builtin
+from pokemon_v2.models import *
TEST_HOST = "http://testserver"
API_V2 = "/api/v2"
@@ -598,13 +598,13 @@ def setup_type_data(cls, name="tp", move_damage_class=None, generation=None):
generation = generation or cls.setup_generation_data(name="rgn for " + name)
- type = Type(name=name, generation=generation, move_damage_class=move_damage_class)
- type.save()
+ type_obj = Type(name=name, generation=generation, move_damage_class=move_damage_class)
+ type_obj.save()
- return type
+ return type_obj
@classmethod
- def setup_type_name_data(cls, type, name="tp nm"):
+ def setup_type_name_data(cls, type, name="tp nm"): # noqa: A002
language = cls.setup_language_data(name="lang for " + name)
type_name = TypeName.objects.create(language=language, name=name, type=type)
@@ -613,7 +613,7 @@ def setup_type_name_data(cls, type, name="tp nm"):
return type_name
@classmethod
- def setup_type_game_index_data(cls, type, game_index=0):
+ def setup_type_game_index_data(cls, type, game_index=0): # noqa: A002
generation = cls.setup_generation_data(name="gen for tp gm indx")
type_game_index = TypeGameIndex.objects.create(type=type, game_index=game_index, generation=generation)
@@ -621,7 +621,7 @@ def setup_type_game_index_data(cls, type, game_index=0):
return type_game_index
- def setup_type_sprites_data(cls, type):
+ def setup_type_sprites_data(self, type): # noqa: A002
game_map = {
"generation-iii": [
"colosseum",
@@ -646,14 +646,14 @@ def setup_type_sprites_data(cls, type):
"generation-ix": ["scarlet-violet"],
}
sprites = {}
- for generation in game_map.keys():
- for game in game_map[generation]:
+ for generation, games in game_map.items():
+ for game in games:
if generation not in sprites:
sprites[generation] = {}
- if type.id == 18 and generation.endswith(("-iii", "-iv", "-v")):
- sprites[generation][game] = None
- elif type.id == 19 and generation.endswith(("-iii", "-iv", "-v", "-vi", "-vii", "-viii")):
+ if (type.id == 18 and generation.endswith(("-iii", "-iv", "-v"))) or (
+ type.id == 19 and generation.endswith(("-iii", "-iv", "-v", "-vi", "-vii", "-viii"))
+ ):
sprites[generation][game] = None
else:
sprites[generation][game] = {
@@ -885,7 +885,7 @@ def setup_move_data(
move_damage_class=None,
move_effect=None,
move_target=None,
- type=None,
+ type=None, # noqa: A002
name="mv",
power=20,
pp=20,
@@ -901,7 +901,7 @@ def setup_move_data(
generation = generation or cls.setup_generation_data(name="gen for " + name)
- type = type or cls.setup_type_data(name="tp for " + name)
+ type_obj = type or cls.setup_type_data(name="tp for " + name)
move_target = move_target or cls.setup_move_target_data(name="mv trgt for " + name)
@@ -910,7 +910,7 @@ def setup_move_data(
move = Move.objects.create(
name=name,
generation=generation,
- type=type,
+ type=type_obj,
power=power,
pp=pp,
accuracy=accuracy,
@@ -980,7 +980,7 @@ def setup_move_meta_data(
def setup_move_change_data(
cls,
move=None,
- type=None,
+ type=None, # noqa: A002
move_effect=None,
version_group=None,
power=20,
@@ -1432,10 +1432,10 @@ def setup_pokemon_form_sprites_data(
return pokemon_form_sprites
@classmethod
- def setup_pokemon_form_type_data(cls, pokemon_form, type=None, slot=1):
- type = type or cls.setup_type_data(name="tp for pkmn frm")
+ def setup_pokemon_form_type_data(cls, pokemon_form, type=None, slot=1): # noqa: A002
+ type_obj = type or cls.setup_type_data(name="tp for pkmn frm")
- form_type = PokemonFormType(pokemon_form=pokemon_form, type=type, slot=slot)
+ form_type = PokemonFormType(pokemon_form=pokemon_form, type=type_obj, slot=slot)
form_type.save()
return form_type
@@ -1542,19 +1542,19 @@ def setup_pokemon_past_stat_data(cls, pokemon, generation, base_stat=10, effort=
return pokemon_stat_past
@classmethod
- def setup_pokemon_type_data(cls, pokemon, type=None, slot=1):
- type = type or cls.setup_type_data(name="tp for pkmn")
+ def setup_pokemon_type_data(cls, pokemon, type=None, slot=1): # noqa: A002
+ type_obj = type or cls.setup_type_data(name="tp for pkmn")
- pokemon_type = PokemonType(pokemon=pokemon, type=type, slot=slot)
+ pokemon_type = PokemonType(pokemon=pokemon, type=type_obj, slot=slot)
pokemon_type.save()
return pokemon_type
@classmethod
- def setup_pokemon_past_type_data(cls, pokemon, generation, type=None, slot=1):
- type = type or cls.setup_type_data(name="tp for pkmn")
+ def setup_pokemon_past_type_data(cls, pokemon, generation, type=None, slot=1): # noqa: A002
+ type_obj = type or cls.setup_type_data(name="tp for pkmn")
- pokemon_type_past = PokemonTypePast(pokemon=pokemon, generation=generation, type=type, slot=slot)
+ pokemon_type_past = PokemonTypePast(pokemon=pokemon, generation=generation, type=type_obj, slot=slot)
pokemon_type_past.save()
return pokemon_type_past
@@ -2026,7 +2026,7 @@ def test_generation_api(self):
ability = self.setup_ability_data(name="ablty for base gen", generation=generation)
move = self.setup_move_data(name="mv for base gen", generation=generation)
pokemon_species = self.setup_pokemon_species_data(name="pkmn spcs for base gen", generation=generation)
- type = self.setup_type_data(name="tp for base gen", generation=generation)
+ type_obj = self.setup_type_data(name="tp for base gen", generation=generation)
version_group = self.setup_version_group_data(name="ver grp for base gen", generation=generation)
response = self.client.get("{}/generation/{}/".format(API_V2, generation.pk))
@@ -2060,10 +2060,10 @@ def test_generation_api(self):
"{}{}/move/{}/".format(TEST_HOST, API_V2, move.pk),
)
# type params
- self.assertEqual(response.data["types"][0]["name"], type.name)
+ self.assertEqual(response.data["types"][0]["name"], type_obj.name)
self.assertEqual(
response.data["types"][0]["url"],
- "{}{}/type/{}/".format(TEST_HOST, API_V2, type.pk),
+ "{}{}/type/{}/".format(TEST_HOST, API_V2, type_obj.pk),
)
# species params
self.assertEqual(response.data["pokemon_species"][0]["name"], pokemon_species.name)
@@ -2604,8 +2604,8 @@ def test_berry_flavor_api(self):
)
def test_berry_api(self):
- type = self.setup_type_data(name="tp fr base bry")
- berry = self.setup_berry_data(name="base bry", natural_gift_type=type)
+ type_obj = self.setup_type_data(name="tp fr base bry")
+ berry = self.setup_berry_data(name="base bry", natural_gift_type=type_obj)
berry_flavor = self.setup_berry_flavor_data(name="bry flvr for base bry")
berry_flavor_map = self.setup_berry_flavor_map_data(berry=berry, berry_flavor=berry_flavor)
@@ -2640,10 +2640,10 @@ def test_berry_api(self):
"{}{}/berry-flavor/{}/".format(TEST_HOST, API_V2, berry_flavor.pk),
)
# natural gift type
- self.assertEqual(response.data["natural_gift_type"]["name"], type.name)
+ self.assertEqual(response.data["natural_gift_type"]["name"], type_obj.name)
self.assertEqual(
response.data["natural_gift_type"]["url"],
- "{}{}/type/{}/".format(TEST_HOST, API_V2, type.pk),
+ "{}{}/type/{}/".format(TEST_HOST, API_V2, type_obj.pk),
)
# Growth Rate Tests
@@ -3166,6 +3166,7 @@ def test_type_api(self):
past_damage_relations = response.data["past_damage_relations"]
gen_data = past_damage_relations[0]["generation"]
self.assertEqual(gen_data["name"], generation.name)
+ assert past_no_damage_to_relation.generation is not None
self.assertEqual(
gen_data["url"],
"{}{}/generation/{}/".format(TEST_HOST, API_V2, past_no_damage_to_relation.generation.pk),
@@ -3229,7 +3230,7 @@ def test_type_api(self):
sprites_data = json.loads(type_sprites.sprites)
- for generation in game_map.keys():
+ for generation in game_map:
for game in game_map[generation]:
self.assertEqual(
json.loads(response.data["sprites"])[generation][game]["name_icon"],
@@ -4282,7 +4283,7 @@ def test_pokemon_api(self):
# assert that we only got one move record back.
pokemon_move = self.setup_move_data(name="mv for pkmn")
pokemon_moves = []
- for move in range(0, 4):
+ for move in range(4):
version_group = self.setup_version_group_data(name="ver grp " + str(move) + " for pkmn")
new_move = self.setup_pokemon_move_data(
pokemon=pokemon,
@@ -4323,6 +4324,10 @@ def test_pokemon_api(self):
self.assertEqual(response.data["height"], pokemon.height)
self.assertEqual(response.data["weight"], pokemon.weight)
self.assertEqual(response.data["base_experience"], pokemon.base_experience)
+ self.assertEqual(
+ response.data["location_area_encounters"],
+ "{}{}/pokemon/{}/encounters".format(TEST_HOST, API_V2, pokemon.pk),
+ )
# species params
self.assertEqual(response.data["species"]["name"], pokemon_species.name)
self.assertEqual(
@@ -4453,23 +4458,23 @@ def test_pokemon_api(self):
for i, val in enumerate(pokemon_moves): # pylint: disable=unused-variable
version = response.data["moves"][0]["version_group_details"][i]
# Learn Level
- expected = pokemon_moves[i].level
+ expected = val.level
actual = version["level_learned_at"]
self.assertEqual(expected, actual)
# Version Group Name
- expected = pokemon_moves[i].version_group.name
+ expected = val.version_group.name
actual = version["version_group"]["name"]
self.assertEqual(expected, actual)
# Version Group URL
- expected = "{}{}/version-group/{}/".format(TEST_HOST, API_V2, pokemon_moves[i].version_group.pk)
+ expected = "{}{}/version-group/{}/".format(TEST_HOST, API_V2, val.version_group.pk)
actual = version["version_group"]["url"]
self.assertEqual(expected, actual)
# Learn Method Name
- expected = pokemon_moves[i].move_learn_method.name
+ expected = val.move_learn_method.name
actual = version["move_learn_method"]["name"]
self.assertEqual(expected, actual)
# Learn Method URL
- expected = "{}{}/move-learn-method/{}/".format(TEST_HOST, API_V2, pokemon_moves[i].move_learn_method.pk)
+ expected = "{}{}/move-learn-method/{}/".format(TEST_HOST, API_V2, val.move_learn_method.pk)
actual = version["move_learn_method"]["url"]
self.assertEqual(expected, actual)
# game indices params
@@ -4495,7 +4500,7 @@ def test_pokemon_api(self):
sprites_data = json.loads(pokemon_sprites.sprites)
cries_data = json.loads(pokemon_cries.cries)
response_sprites_data = json.loads(response.data["sprites"])
- response_cries_data = json.loads(response.data["cries"])
+ json.loads(response.data["cries"])
# sprite params
self.assertEqual(
@@ -4853,28 +4858,28 @@ def test_evolution_chain_api_wurmple_bugfix(self):
evolves_from_species=basic,
evolution_chain=evolution_chain,
)
- stage_one_first_evolution = self.setup_pokemon_evolution_data(evolved_species=stage_one_first, min_level=7)
+ self.setup_pokemon_evolution_data(evolved_species=stage_one_first, min_level=7)
stage_two_first = self.setup_pokemon_species_data(
name="beautifly",
evolves_from_species=stage_one_first,
evolution_chain=evolution_chain,
)
- stage_two_first_evolution = self.setup_pokemon_evolution_data(evolved_species=stage_two_first, min_level=10)
+ self.setup_pokemon_evolution_data(evolved_species=stage_two_first, min_level=10)
stage_one_second = self.setup_pokemon_species_data(
name="cascoon",
evolves_from_species=basic,
evolution_chain=evolution_chain,
)
- stage_one_second_evolution = self.setup_pokemon_evolution_data(evolved_species=stage_one_second, min_level=7)
+ self.setup_pokemon_evolution_data(evolved_species=stage_one_second, min_level=7)
stage_two_second = self.setup_pokemon_species_data(
name="dustox",
evolves_from_species=stage_one_second,
evolution_chain=evolution_chain,
)
- stage_two_second_evolution = self.setup_pokemon_evolution_data(evolved_species=stage_two_second, min_level=10)
+ self.setup_pokemon_evolution_data(evolved_species=stage_two_second, min_level=10)
response = self.client.get("{}/evolution-chain/{}/".format(API_V2, evolution_chain.pk))
@@ -5037,24 +5042,24 @@ def test_case_insensitive_api(self):
# Set up pokemon data
pokemon_species = self.setup_pokemon_species_data(name="pkmn spcs for base pkmn")
pokemon = self.setup_pokemon_data(pokemon_species=pokemon_species, name="base pkm")
- pokemon_form = self.setup_pokemon_form_data(pokemon=pokemon, name="pkm form for base pkmn")
+ self.setup_pokemon_form_data(pokemon=pokemon, name="pkm form for base pkmn")
generation = self.setup_generation_data(name="base gen")
- pokemon_ability = self.setup_pokemon_ability_data(pokemon=pokemon)
- pokemon_past_ability = self.setup_pokemon_past_ability_data(pokemon=pokemon, generation=generation)
- pokemon_stat = self.setup_pokemon_stat_data(pokemon=pokemon)
- pokemon_past_stat = self.setup_pokemon_past_stat_data(pokemon=pokemon, generation=generation)
- pokemon_type = self.setup_pokemon_type_data(pokemon=pokemon)
- pokemon_past_type = self.setup_pokemon_past_type_data(pokemon=pokemon, generation=generation)
- pokemon_item = self.setup_pokemon_item_data(pokemon=pokemon)
- pokemon_sprites = self.setup_pokemon_sprites_data(pokemon=pokemon)
- pokemon_cries = self.setup_pokemon_cries_data(pokemon, latest=True, legacy=True)
- pokemon_game_index = self.setup_pokemon_game_index_data(pokemon=pokemon, game_index=10)
+ self.setup_pokemon_ability_data(pokemon=pokemon)
+ self.setup_pokemon_past_ability_data(pokemon=pokemon, generation=generation)
+ self.setup_pokemon_stat_data(pokemon=pokemon)
+ self.setup_pokemon_past_stat_data(pokemon=pokemon, generation=generation)
+ self.setup_pokemon_type_data(pokemon=pokemon)
+ self.setup_pokemon_past_type_data(pokemon=pokemon, generation=generation)
+ self.setup_pokemon_item_data(pokemon=pokemon)
+ self.setup_pokemon_sprites_data(pokemon=pokemon)
+ self.setup_pokemon_cries_data(pokemon, latest=True, legacy=True)
+ self.setup_pokemon_game_index_data(pokemon=pokemon, game_index=10)
# To test issue #85, we will create one move that has multiple
# learn levels in different version groups. Later, we'll
# assert that we only got one move record back.
pokemon_move = self.setup_move_data(name="mv for pkmn")
pokemon_moves = []
- for move in range(0, 4):
+ for move in range(4):
version_group = self.setup_version_group_data(name="ver grp " + str(move) + " for pkmn")
new_move = self.setup_pokemon_move_data(
pokemon=pokemon,
@@ -5105,7 +5110,7 @@ def test_case_insensitive_api(self):
# Same test with /language endpoint
language = self.setup_language_data(name="base-lang")
- language_name = self.setup_language_name_data(language, name="base-lang-name")
+ self.setup_language_name_data(language, name="base-lang-name")
lowercase_name = language.name.lower()
uppercase_name = language.name.upper()
@@ -5128,7 +5133,7 @@ def test_case_insensitive_api(self):
def test_meta_api(self):
response = self.client.get("{}/meta/".format(API_V2))
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(datetime.fromtimestamp(int(response.data["deploy_date"])))
+ self.assertTrue(datetime.fromtimestamp(int(response.data["deploy_date"]), tz=timezone.utc))
self.assertEqual(10, len(response.data["deploy_date"]))
self.assertEqual(40, len(response.data["hash"]))
self.assertIn("tag", response.data)
diff --git a/pokemon_v2/urls.py b/pokemon_v2/urls.py
index 564dab604..05034625b 100644
--- a/pokemon_v2/urls.py
+++ b/pokemon_v2/urls.py
@@ -1,3 +1,4 @@
+# ruff: noqa: F405
#####################################
#
# V2 API setup using Django Rest
@@ -8,11 +9,12 @@
from typing import TYPE_CHECKING, Any
-from django.urls import include, path, re_path
+from django.urls import URLPattern, URLResolver, include, path, re_path
from rest_framework import routers
from rest_framework.reverse import reverse as drf_reverse
+from typing_extensions import override
-from pokemon_v2.api import *
+from pokemon_v2.api import * # noqa: F403
if TYPE_CHECKING:
from rest_framework.request import Request
@@ -20,6 +22,7 @@
class PokeAPIRootView(routers.APIRootView):
+ @override
def get(self, request: Request, *args: Any, **kwargs: Any) -> Response:
response = super().get(request, *args, **kwargs)
response.data["meta"] = drf_reverse("meta", request=request)
@@ -91,7 +94,7 @@ class PokeAPIRouter(routers.DefaultRouter):
#
###########################
-urlpatterns = [
+urlpatterns: list[URLPattern | URLResolver] = [
path("api/v2/meta/", PokeapiMetaView.as_view(), name="meta"),
path("api/v2/", include(router.urls)),
re_path(
diff --git a/pyproject.toml b/pyproject.toml
index 117445cf9..3918e6630 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,11 +15,15 @@ dependencies = [
"gunicorn==23.0.0",
"legacy-cgi>=2.6.4 ; python_full_version >= '3.13'",
"psycopg[binary]==3.3.2",
+ "typing-extensions>=4.15.0",
]
[dependency-groups]
dev = [
"coverage==7.13.1",
+ "django-types>=0.24.0",
+ "djangorestframework-stubs>=3.17.1",
"pre-commit>=4.6.0",
"ruff>=0.15.21",
+ "ty>=0.0.65",
]
diff --git a/ruff.toml b/ruff.toml
index 4e6fb552a..0f30d9c3c 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -4,3 +4,38 @@ exclude = [
"data/v2/cries",
"data/v2/sprites",
]
+
+[lint]
+extend-select = [
+ "A", # flake8-builtins: shadowing builtins
+ "ANN", # flake8-annotations: missing type annotations
+ "B", # flake8-bugbear: likely bugs
+ "C4", # flake8-comprehensions: simplify comprehensions
+ "DJ", # flake8-django: django best practices
+ "E", # pycodestyle: PEP 8 errors
+ "ERA", # eradicate: commented-out code
+ "FURB", # refurb: modernize and simplify
+ "I", # isort: import sorting
+ "N", # pep8-naming: naming conventions
+ "PERF", # Perflint: performance anti-patterns
+ "PGH003", # pygrep-hooks: blanket type: ignore
+ "PYI", # flake8-pyi: stub file best practices
+ "RUF", # Ruff-specific: assorted rules
+ "SIM", # flake8-simplify: simplify expressions
+ "TC", # flake8-type-checking: TYPE_CHECKING imports
+ "TID", # flake8-tidy-imports: tidy imports
+ "F", # Pyflakes: core Python error detection
+ "UP", # pyupgrade: modern Python syntax
+ "W", # pycodestyle: PEP 8 warnings
+]
+preview = true
+
+[lint.per-file-ignores]
+# TODO: Test files
+"pokemon_v2/tests.py" = ["ANN", "UP032", "F403", "F405"]
+"pokemon_v2/test_models.py" = ["ANN", "F403", "F405"]
+# TODO: Data loading scripts
+"data/v2/build.py" = ["ANN", "ERA001", "F403", "F405"]
+"data/v2/__init__.py" = ["F403", "F405"]
+# Django migration generated code
+"pokemon_v2/migrations/*.py" = ["RUF012", "ERA001"]
diff --git a/ty.toml b/ty.toml
new file mode 100644
index 000000000..860da59c7
--- /dev/null
+++ b/ty.toml
@@ -0,0 +1,16 @@
+[src]
+include = [
+ "config",
+ "data/v2/*.py",
+ "pokemon_v2",
+]
+
+[rules]
+blanket-ignore-comment = "error"
+missing-override-decorator = "error"
+missing-type-argument = "error"
+
+division-by-zero = "warn"
+possibly-missing-attribute = "warn"
+possibly-missing-import = "warn"
+possibly-unresolved-reference = "warn"
diff --git a/uv.lock b/uv.lock
index bb5135060..14efaac51 100644
--- a/uv.lock
+++ b/uv.lock
@@ -206,6 +206,47 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/79/055dfcc508cfe9f439d9f453741188d633efa9eab90fc78a67b0ab50b137/django_redis-6.0.0-py3-none-any.whl", hash = "sha256:20bf0063a8abee567eb5f77f375143c32810c8700c0674ced34737f8de4e36c0", size = 33687, upload-time = "2025-06-17T18:15:34.165Z" },
]
+[[package]]
+name = "django-stubs"
+version = "6.0.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+ { name = "django-stubs-ext" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "types-pyyaml" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/90/087c6e424e705e05182e543ef6b366a59eb5c92ab008b0dbbba55f357a40/django_stubs-6.0.7.tar.gz", hash = "sha256:bc55431c0af745a64e39cf33a8d36c87dccbedeae2fe26fab47dd355270e8538", size = 282293, upload-time = "2026-07-14T10:08:27.122Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/02/86/230ae6056221b543d63f7710d73967fe1c6840693540e99ea3c54f45859c/django_stubs-6.0.7-py3-none-any.whl", hash = "sha256:7ed9a14c438e589272ca04e966dee82a4d1ff7ca5c2171bc986c50a0d03ec35b", size = 547460, upload-time = "2026-07-14T10:08:25.626Z" },
+]
+
+[[package]]
+name = "django-stubs-ext"
+version = "6.0.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/36/50/917f7224ea470e89cdcdc93d3dfe75b8391adf976cf12f2ecdb5f5d122be/django_stubs_ext-6.0.7.tar.gz", hash = "sha256:c3172c5126614fd2a44d0196b313b44c21f717cb09477ba52b447d41f4ce613e", size = 6665, upload-time = "2026-07-14T10:07:56.933Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/65/4d73fce956b5ebf26449259664360e539fbe95f0e86be396c28b636ba72a/django_stubs_ext-6.0.7-py3-none-any.whl", hash = "sha256:53a9c7c5a7c7e718cc6308cfce1e7470f2cac0b9d38dbcd60fbfa82704f1d592", size = 10362, upload-time = "2026-07-14T10:07:55.653Z" },
+]
+
+[[package]]
+name = "django-types"
+version = "0.24.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "types-psycopg2" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d7/7b/8e05b8631fa7de84038d4f24fa57e983107aff889a6d88a9c40a21e15d1c/django_types-0.24.0.tar.gz", hash = "sha256:af903de8b9ee963b7594459a7a20cb8eaaab176ae2b3244ecaa089e0c570b0d1", size = 208426, upload-time = "2026-04-22T22:19:01.999Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2f/ab/f5c37ecc08c396df62579246597796697b58253c8b30206870e8d0f644d0/django_types-0.24.0-py3-none-any.whl", hash = "sha256:ddb478ca733e0dde5475118dd59ab340156980f9659fd92de2083326ae96100a", size = 379436, upload-time = "2026-04-22T22:19:03.368Z" },
+]
+
[[package]]
name = "djangorestframework"
version = "3.16.1"
@@ -218,6 +259,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/ce/bf8b9d3f415be4ac5588545b5fcdbbb841977db1c1d923f7568eeabe1689/djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec", size = 1080442, upload-time = "2025-08-06T17:50:50.667Z" },
]
+[[package]]
+name = "djangorestframework-stubs"
+version = "3.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django-stubs" },
+ { name = "types-pyyaml" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/4b/1bee065c6c87ee04b905732deb53acdfc43d0e5673f43a5f75ec4deb7f6f/djangorestframework_stubs-3.17.1.tar.gz", hash = "sha256:5b67655090e2976b778bad572840b732a87ab2911402530d0dca2fbf72f891c5", size = 33470, upload-time = "2026-07-28T21:27:52.071Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/86/38/6a04ffca56e24220bf37494fe9f75ce9abe8115776a90c924732bb37823a/djangorestframework_stubs-3.17.1-py3-none-any.whl", hash = "sha256:40f1f22ec1965d1caff17c66794b87799eb8f42e0628ea46b501dcd565d8a803", size = 57562, upload-time = "2026-07-28T21:27:50.58Z" },
+]
+
[[package]]
name = "drf-spectacular"
version = "0.29.0"
@@ -352,13 +407,17 @@ dependencies = [
{ name = "gunicorn" },
{ name = "legacy-cgi", marker = "python_full_version >= '3.13'" },
{ name = "psycopg", extra = ["binary"] },
+ { name = "typing-extensions" },
]
[package.dev-dependencies]
dev = [
{ name = "coverage" },
+ { name = "django-types" },
+ { name = "djangorestframework-stubs" },
{ name = "pre-commit" },
{ name = "ruff" },
+ { name = "ty" },
]
[package.metadata]
@@ -372,13 +431,17 @@ requires-dist = [
{ name = "gunicorn", specifier = "==23.0.0" },
{ name = "legacy-cgi", marker = "python_full_version >= '3.13'", specifier = ">=2.6.4" },
{ name = "psycopg", extras = ["binary"], specifier = "==3.3.2" },
+ { name = "typing-extensions", specifier = ">=4.15.0" },
]
[package.metadata.requires-dev]
dev = [
{ name = "coverage", specifier = "==7.13.1" },
+ { name = "django-types", specifier = ">=0.24.0" },
+ { name = "djangorestframework-stubs", specifier = ">=3.17.1" },
{ name = "pre-commit", specifier = ">=4.6.0" },
{ name = "ruff", specifier = ">=0.15.21" },
+ { name = "ty", specifier = ">=0.0.65" },
]
[[package]]
@@ -852,6 +915,103 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
]
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "ty"
+version = "0.0.65"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cf561927e8e9ab5c1892a833b664aa9cd6f051a75f6280c66d8047246bda/ty-0.0.65.tar.gz", hash = "sha256:b7134bffcc00b715fa8291e84d845782ced810a998dc1f7f11d71c85c4046325", size = 6460098, upload-time = "2026-07-29T18:31:03.27Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/4e/71e2d325d2b53a1afad81624ad076b2ede413213fc4a18cb05b78c568571/ty-0.0.65-py3-none-linux_armv6l.whl", hash = "sha256:dc556c9f05408bef4c4ef02b2cc382e4e5f797b4b20d64410289848f0d76705f", size = 12298466, upload-time = "2026-07-29T18:30:12.744Z" },
+ { url = "https://files.pythonhosted.org/packages/57/77/fec8f29647c55794efa430a7f365e44f5ce7ffb6459d9445a87fac569bec/ty-0.0.65-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d2e0d34cc0a28a17ef0cf81135c5ebabc3562131f9079138ba5e7bae0f56bd", size = 11942421, upload-time = "2026-07-29T18:30:16.076Z" },
+ { url = "https://files.pythonhosted.org/packages/13/09/7f3766aef9dc627e2698cf4e3e59cf53389dcae3812040d33c1aa931230f/ty-0.0.65-py3-none-macosx_11_0_arm64.whl", hash = "sha256:685f49a9312bbf69d5b65bbb66384fed1f927403ea030c217b9289092d7e46c4", size = 11451922, upload-time = "2026-07-29T18:30:19.155Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/7b/1a77cd50e0befb50f55b8bf9bd3ed3eddf184bf28c61b56727039e0774fc/ty-0.0.65-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f564b5ebe78e2f3a8e7b8eacb1292eb88b7c0f3c8630671cfca31abc0709cd9", size = 11994999, upload-time = "2026-07-29T18:30:22.315Z" },
+ { url = "https://files.pythonhosted.org/packages/63/7b/feda16f3a4a0a99be27431e0c9598eeeec0db1eb2fec9a15976698209418/ty-0.0.65-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c983e156fe9e113fb56389e13d327b6b8549fe866de9b269684723a88e9b732d", size = 12090662, upload-time = "2026-07-29T18:30:24.93Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/3e/3f69bf9c9307dbdc0771719f65ce5b556e7bdeeaccbdd599d4f57866d801/ty-0.0.65-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3663b7396e8b1a9954e20e732de7ccb0192bf4118473069b4945920d6923921", size = 12822094, upload-time = "2026-07-29T18:30:28.012Z" },
+ { url = "https://files.pythonhosted.org/packages/90/38/8fa791b3bb503ee2b46ad81690cd1bdd54519582df6d805cee57fe143e85/ty-0.0.65-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306ed01f29d6e108e98feb233dbbf5878a027603b71bd3743b343977933a9f16", size = 13357833, upload-time = "2026-07-29T18:30:31.122Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/73/4dda396a201e1dd0ed3594a9b48e559cb41c4bc048c6cd4c4d1b39eb4313/ty-0.0.65-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28bcfc8898c94f079a9100e684bcf312b6a64ad3a7d4ebb35a4591546030a2cd", size = 12977303, upload-time = "2026-07-29T18:30:33.944Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/26/c250c2c569adc53a8591716641388397bcb2a442e4a30b952ae81b50c0e0/ty-0.0.65-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a75bd0c245c38802a8f488378e74f92feb7dd33db7d63fbdd6fdf82791ba730", size = 12579338, upload-time = "2026-07-29T18:30:37.199Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/94/4a5647d44753ca218fc930d7e4d9bf468d0ed4a0ad4b3d57588bc1bbacf7/ty-0.0.65-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9e5e1bdea9662d2b5312b4e99f319f4e6e2ea427511b5fbc546141b79ec53f76", size = 12957731, upload-time = "2026-07-29T18:30:39.937Z" },
+ { url = "https://files.pythonhosted.org/packages/36/b6/1e22fa11a1e0dfb20b1c7f3cbfd8170273aada2a82f9ecd3055275370c44/ty-0.0.65-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:03a88493d4842889f65280ae241e06b399d57eb3c63571054cad21a4c33b3b69", size = 11938625, upload-time = "2026-07-29T18:30:42.603Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/0a/fe5f22ef62b193201bc5566762e22049762cd485bfafb5095a7050760054/ty-0.0.65-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:600b8bf6f4940cf7ffb2f43d3716faaf38dcb97cd8617c55771451bc0276408f", size = 12105592, upload-time = "2026-07-29T18:30:45.419Z" },
+ { url = "https://files.pythonhosted.org/packages/76/fd/922b3a6e9d697452cdbb4b7e3f636868add5ec652154518a736e4364f3b7/ty-0.0.65-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c28007bc79d648c1ddaf1e65885d07baec48eb87240da442f608e4107c1b7d8", size = 12387335, upload-time = "2026-07-29T18:30:48.405Z" },
+ { url = "https://files.pythonhosted.org/packages/77/22/a1a08ebc84c083db2fb55e3b5cd186db0c067692f4921146f601360231e2/ty-0.0.65-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c852da96091ad22361e6586b7c7ba98e1334dcd4d8ffb67e47f4fb673de33f77", size = 12682710, upload-time = "2026-07-29T18:30:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/81/14/eaaa410a25bbdea19722109b5422380a0e211b3afcf3071d15953ddbd5db/ty-0.0.65-py3-none-win32.whl", hash = "sha256:cf529d538f1403b14b0511e6ec3cdb95d3d974adabf24cc76cedc533368c3edc", size = 11692341, upload-time = "2026-07-29T18:30:54.35Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/0f/6d48f206dce9d7e53fe3b5ea0f0ab5800dd9d2365b2b48f736783436c43f/ty-0.0.65-py3-none-win_amd64.whl", hash = "sha256:234a321e33c7cbbfbd67bfa0b01b685dd9c21f1841781a21e5ca1fa0b25f1d5d", size = 12729355, upload-time = "2026-07-29T18:30:57.275Z" },
+ { url = "https://files.pythonhosted.org/packages/96/aa/7446f7725e303cf78e058c893af1f0552b9451895454908706f4c6c3494b/ty-0.0.65-py3-none-win_arm64.whl", hash = "sha256:b9424be1ec56d93ff18609fb1c0a0a2283fe1282cd6d1c7604f97d73b94d61f2", size = 12051375, upload-time = "2026-07-29T18:31:00.579Z" },
+]
+
+[[package]]
+name = "types-psycopg2"
+version = "2.9.21.20260724"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/29/09ca8f0ad16105193deabcaf8256059e361e9e37ff24551efd9c739e240b/types_psycopg2-2.9.21.20260724.tar.gz", hash = "sha256:db31031c37de823a2b21c787cb84832174fdda2a99a8b1af648206f19cc32f8c", size = 27644, upload-time = "2026-07-24T04:57:36.61Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/90/1e77eb76030bb5feb249ecb41cd7647c6ae831ec7bd2b61a61beecac4029/types_psycopg2-2.9.21.20260724-py3-none-any.whl", hash = "sha256:4f87890dd06cea99e3bb0536330ffc2a7320e69059dc1ee0e2c884ef2718f2b4", size = 24964, upload-time = "2026-07-24T04:57:35.703Z" },
+]
+
+[[package]]
+name = "types-pyyaml"
+version = "6.0.12.20260724"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/6f/a28f44bcd56bebed42b028a2894c79853e2f5e6b5279e633cb3f287a05e7/types_pyyaml-6.0.12.20260724.tar.gz", hash = "sha256:3c1ce1bb73cd5ec02e90390c2b1f00e810d241d8825fd73ff359696839271b6b", size = 17893, upload-time = "2026-07-24T04:58:43.453Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8b/42/0337fefc615e20ee55d1c8f71b774a9b2b734a04669139c20753b27a2a3a/types_pyyaml-6.0.12.20260724-py3-none-any.whl", hash = "sha256:d57db930a4b2efbc57cf430ec8882765d246929432fa253092f383902329a453", size = 20312, upload-time = "2026-07-24T04:58:42.486Z" },
+]
+
[[package]]
name = "typing-extensions"
version = "4.15.0"