{"id":30586155,"url":"https://github.com/pprunty/poke-sdk","last_synced_at":"2025-08-29T11:43:49.949Z","repository":{"id":309545394,"uuid":"1035948237","full_name":"pprunty/poke-sdk","owner":"pprunty","description":"Python SDK for PokeAPI APIs","archived":false,"fork":false,"pushed_at":"2025-08-12T12:58:04.000Z","size":4157,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-12T14:38:55.305Z","etag":null,"topics":["api","async","http","httpx","pokeapi","pokemon","python","sdk"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pprunty.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-11T10:35:18.000Z","updated_at":"2025-08-12T12:58:08.000Z","dependencies_parsed_at":"2025-08-12T14:38:57.922Z","dependency_job_id":"42abb18f-5867-48c3-b909-342a28ebeafa","html_url":"https://github.com/pprunty/poke-sdk","commit_stats":null,"previous_names":["pprunty/poke-sdk"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/pprunty/poke-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fpoke-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fpoke-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fpoke-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fpoke-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pprunty","download_url":"https://codeload.github.com/pprunty/poke-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fpoke-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272679392,"owners_count":24975255,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-29T02:00:10.610Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","async","http","httpx","pokeapi","pokemon","python","sdk"],"created_at":"2025-08-29T11:43:49.295Z","updated_at":"2025-08-29T11:43:49.926Z","avatar_url":"https://github.com/pprunty.png","language":"Python","readme":"# PokeAPI Python API Library\n\n[![PyPI version](https://badge.fury.io/py/poke-sdk.svg)](https://badge.fury.io/py/poke-sdk)\n\n![header](./header.png)\n\nThe PokeAPI Python Library provides access to [PokeAPI](https://pokeapi.co/) APIs from Python 3.8+ applications. The library includes type definitions for PokeAPI endpoints (`/pokemon/{id or name}` and `/generation/{id or name}`), plus custom high-level resources built on top of the API for enhanced functionality. The library also offers both synchronous and asynchronous clients, pagination (with lazy loading), and custom caching and retries powered by httpx, cachetools and pydantic libraries.\n\nThanks to [PokeAPI](https://pokeapi.co/) for maintaining and making the APIs publicly accessible.\n\n## Documentation\n\nThe REST API documentation can be found at \u003cpokeapi.co/docs\u003e. The live site crashes... always. But the APIs are pretty reliable.\n\n## Requirements\n\n* python 3.8+\n* [Poetry](https://python-poetry.org/) python dependency and project manager (`curl -sSL https://install.python-poetry.org | python3 -` or `pip install poetry`)\n\n## Installation\n\n### Development\n\n```bash\ngit clone https://github.com/pprunty/poke-sdk.git \u0026\u0026 cd poke-sdk\n```\n\n```bash\nmake install\n```\n\nor:\n\n```bash\npoetry install\n```\n\n**Run examples**:\n\n```bash\nmake example \u003cexample_file_path\u003e \n```\nThe file path should start with `examples/*`\n\n### pip\n\n\u003e [!WARNING]\n\u003e This package has not been published to PyPI yet. Use the development installation method above.\n\n```bash\npip install poke-sdk\n```\n\n## Usage\n\n```python\nfrom poke_api import Poke\n\nclient = Poke()\n\n# Get a Pokemon by ID or name\npikachu = client.pokemon.get(id=25)\nprint(pikachu)\n# Output: Pokemon(id=25, name='pikachu', lists={abilities: 2, forms: 1, moves: 105, types: 1})\n```\n\n## Async Usage\n\n```python\nfrom poke_api import AsyncPoke\n\nclient = AsyncPoke()\n\nasync def main() -\u003e None:\n    pikachu = await client.pokemon.get(name=\"pikachu\")\n    print(pikachu)\n```\n\n## Resources\n\n### Core API Resources\nDirect wrappers around PokeAPI endpoints:\n\n- **`client.pokemon.get/list()`**: `/pokemon/{id or name}` - Individual Pokemon data\n- **`client.generation.get/list()`**: `/generation/{id or name}` - Generation information\n\nThese resources are subclasses which inherit from the `[BaseResource](./src/poke_api/_resource.py)`.\n\n### Custom Resources\nHigh-level functionality built on top of the PokeAPI:\n\n- **`client.pokedex.detail/ranking()`**: Comprehensive Pokemon views with rankings and detailed information (like [serebii.net](https://serebii.net/pokedex))\n- **`client.search.pokemon/generation().`**: Cross-resource search capabilities\n\nNote: The `pokedex` and `search` custom resources are not based on the `[BaseResource](./src/poke_api/_resource.py)` and therefore do not inherit methods for `list` and `get`.\n\n## Object Representation\n\nAll models inherit from `BaseModel` and provide:\n* `.to_dict()` - Convert to dictionary representation\n* `.to_json()` - Convert to JSON string representation\n* `.summary()` - Multi-line pretty summary\n\n```python\n# Friendly printing (default behavior)\npikachu = client.pokemon.get(\"pikachu\")\nprint(pikachu)\n# Output: Pokemon(id=25, name='pikachu', lists={abilities: 2, moves: 105, types: 1})\n\n# Multi-line summary\nprint(pikachu.summary())\n# Output:\n# Pokemon:\n#   id: 25\n#   name: pikachu\n#   height: 4\n#   weight: 60\n#   abilities: 2 items\n#   moves: 105 items\n\n# Full data access\npokemon_dict = pikachu.to_dict()    # Returns dictionary with all fields\npokemon_json = pikachu.to_json()    # Returns JSON string with all data\n```\n\n## Pagination\n\nList methods in the PokeAPI are paginated.\n\nThis library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:\n\n```python\nfrom poke_api import Poke\n\nclient = Poke()\n\nall_pokemon = []\n# Automatically fetches more pages as needed.\n# Note: This will fetch ALL Pokemon (1300+) - use limit for demos\nfor pokemon in client.pokemon.list():  # Limit for demo\n    # Do something with pokemon here\n    all_pokemon.append(pokemon)\nprint(all_pokemon)\n```\n\nOr, asynchronously:\n\n```python\nimport asyncio\nfrom poke_api import AsyncPoke\n\nclient = AsyncPoke()\n\nasync def main() -\u003e None:\n    all_pokemon = []\n    # Iterate through items across all pages, issuing requests as needed.\n    # Note: This will fetch ALL Pokemon (1300+) - use limit for demos\n    async for pokemon in client.pokemon.list(limit=50):  # Limit for demo\n        all_pokemon.append(pokemon)\n    print(all_pokemon)\n\nasyncio.run(main())\n```\n\nAlternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:\n\n```python\nfirst_page = client.pokemon.list()\nif first_page.has_next_page():\n    print(f\"will fetch next page using these details: {first_page.next_page_info()}\")\n    next_page = first_page.get_next_page()\n    print(f\"number of items we just fetched: {len(next_page.result)}\")\n\n# Add `await` for async usage.\n```\n\nOr just work directly with the returned data:\n\n```python\nfirst_page = client.pokemon.list()\nfor pokemon in first_page.result:\n    print(pokemon.name)\n\n# Add `await` for async usage.\n```\n\n### Link Expansion (Optional)\n\n`expand` resolves selected `name/url` references into full objects, bounded by depth and max requests.\nIt returns a **dict copy** of your model, leaving the original model untouched. Expanded refs store data\nunder a reserved `__expanded__` key.\n\n```python\n# Async example\nfrom poke_api import AsyncPoke\n\nclient = AsyncPoke()\n\nasync def main():\n    # Get a Pokemon \n    bulba = await client.pokemon.get(\"bulbasaur\")\n    \n    # Expand the first move reference to get full move data\n    expanded = await client.expand(\n        bulba, \n        paths=[\"moves.move\"],  # Only expand move references in the moves array\n        depth=1, \n        max_requests=50, \n        concurrency=6\n    )\n    \n    # Access expanded data via __expanded__ key\n    first_move = expanded[\"moves\"][0][\"move\"]\n    print(first_move[\"name\"])  # -\u003e \"razor-wind\" (original name)\n    print(first_move[\"__expanded__\"][\"name\"])  # -\u003e \"razor-wind\" (full move data name)\n    print(first_move[\"__expanded__\"][\"power\"])  # -\u003e 80 (move power from expanded data)\n\n# Sync example  \nfrom poke_api import Poke\n\nwith Poke() as client:\n    bulba = client.pokemon.get(\"bulbasaur\")\n    expanded = client.expand(\n        bulba, \n        paths=[\"moves.move\"], \n        depth=1, \n        max_requests=50\n    )\n    \n    # Same access pattern\n    move_data = expanded[\"moves\"][0][\"move\"][\"__expanded__\"]\n    print(f\"Move: {move_data['name']}, Power: {move_data.get('power', 'N/A')}\")\n```\n\n**Key Features:**\n- **Non-invasive**: Original models are never modified\n- **Path filtering**: Use `paths=[\"moves.move\", \"species\"]` to target specific references\n- **Depth control**: `depth=2` will expand references within expanded data\n- **Request budgeting**: `max_requests=100` prevents runaway API usage\n- **Async concurrency**: `concurrency=6` controls parallel requests\n- **Automatic deduplication**: Same URLs are only fetched once\n- **Cache integration**: Uses existing client caching and retry logic\n\n**Usage Notes:**\n- Use small `max_requests` and `concurrency` values to be API-friendly\n- Expansion is breadth-first by depth level  \n- The `__expanded__` key is reserved - avoid using it in your data\n- Data is read via the client's normal request layer (benefits from caching, retries, error handling)\n\n### Pokedex (serebii like views)\n\n\u003e **Note**: This is a custom resource built on top of the PokeAPI, not a direct endpoint wrapper. It combines multiple API calls to provide Serebii-style comprehensive Pokemon views.\n\n\nComprehensive Pokedex data with rankings tables and detailed Pokemon views. Inspired by views seen on [serebii.net pokedex](https://serebii.net/pokedex).\n\n#### Rankings:\n\n```python\nfrom poke_api import AsyncPoke, Poke\n\njohto_rankings = await client.pokedex.rankings(generation=2) # sort_by=\"total\" default\ndata = [p.to_dict() for p in johto_rankings]\nprint(json.dumps(data, indent=2, ensure_ascii=False))\n```\n\nExample ranking output:\n\n```json\n[\n  {\n    \"rank\": 1,\n    \"regional_no\": 247,\n    \"national_no\": 249,\n    \"name\": \"lugia\",\n    \"types\": [\n      \"psychic\",\n      \"flying\"\n    ],\n    \"base_stats\": {\n      \"hp\": 106,\n      \"attack\": 90,\n      \"defense\": 130,\n      \"special-attack\": 90,\n      \"special-defense\": 154,\n      \"speed\": 110\n    },\n    \"total_base_stat\": 680,\n    \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/249.png\"\n  },\n  {\n    \"rank\": 2,\n    \"regional_no\": 248,\n    \"national_no\": 250,\n    \"name\": \"ho-oh\",\n    \"types\": [\n      \"fire\",\n      \"flying\"\n    ],\n    \"base_stats\": {\n      \"hp\": 106,\n      \"attack\": 130,\n      \"defense\": 90,\n      \"special-attack\": 110,\n      \"special-defense\": 154,\n      \"speed\": 90\n    },\n    \"total_base_stat\": 680,\n    \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/250.png\"\n  },\n  {\n    \"rank\": 3,\n    \"regional_no\": 249,\n    \"national_no\": 150,\n    \"name\": \"mewtwo\",\n    \"types\": [\n      \"psychic\"\n    ],\n    \"base_stats\": {\n      \"hp\": 106,\n      \"attack\": 110,\n      \"defense\": 90,\n      \"special-attack\": 154,\n      \"special-defense\": 90,\n      \"speed\": 130\n    },\n    \"total_base_stat\": 680,\n    \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/150.png\"\n  },\n  {\n    \"rank\": 4,\n    \"regional_no\": 243,\n    \"national_no\": 149,\n    \"name\": \"dragonite\",\n    \"types\": [\n      \"dragon\",\n      \"flying\"\n    ],\n    \"base_stats\": {\n      \"hp\": 91,\n      \"attack\": 134,\n      \"defense\": 95,\n      \"special-attack\": 100,\n      \"special-defense\": 100,\n      \"speed\": 80\n    },\n    \"total_base_stat\": 600,\n    \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/149.png\"\n  },\n  {\n    \"rank\": 5,\n    \"regional_no\": 246,\n    \"national_no\": 248,\n    \"name\": \"tyranitar\",\n    \"types\": [\n      \"rock\",\n      \"dark\"\n    ],\n    \"base_stats\": {\n      \"hp\": 100,\n      \"attack\": 134,\n      \"defense\": 110,\n      \"special-attack\": 95,\n      \"special-defense\": 100,\n      \"speed\": 61\n    },\n    \"total_base_stat\": 600,\n    \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/248.png\"\n  },\n...\n]\n```\n\n#### Detail:\n\n\n```python\nfrom poke_api import AsyncPoke, Poke\n\n# Get by number\nmewtwo_detail = await client.pokedex.detail(generation=1, number=150)\n\n# Get by name\nmewtwo_detail = await client.pokedex.detail(generation=1, name=\"mewtwo\")\n\nprint(mewtwo_detail.to_json())\n```\n\nExample detail output: \n\n```json\n{\n  \"name\": \"mewtwo\",\n  \"other_names\": [\n    {\"language\": \"ja-Hrkt\", \"value\": \"ミュウツー\"},\n    {\"language\": \"ko\", \"value\": \"뮤츠\"},\n    {\"language\": \"zh-Hant\", \"value\": \"超夢\"},\n    {\"language\": \"fr\", \"value\": \"Mewtwo\"},\n    {\"language\": \"de\", \"value\": \"Mewtu\"},\n    ...\n  ],\n  \"national_no\": 150,\n  \"regional_no\": 150,\n  \"gender_ratio\": \"Genderless\",\n  \"types\": [\"psychic\"],\n  \"classification\": \"Genetic Pokémon\",\n  \"height_m\": 2.0,\n  \"height_ft_in\": \"6'07\\\"\",\n  \"weight_kg\": 122.0,\n  \"weight_lbs\": 269.0,\n  \"capture_rate\": 3,\n  \"base_egg_steps\": 30855,\n  \"growth_rate\": \"slow\",\n  \"base_happiness\": 0,\n  \"ev_yields\": [{\"stat\": \"special-attack\", \"value\": 3}],\n  \"damage_taken\": [\n    {\"type\": \"fighting\", \"multiplier\": 0.5},\n    {\"type\": \"bug\", \"multiplier\": 2.0},\n    {\"type\": \"ghost\", \"multiplier\": 2.0},\n    {\"type\": \"psychic\", \"multiplier\": 0.5},\n    {\"type\": \"dark\", \"multiplier\": 2.0},\n    ...\n  ],\n  \"wild_held_items\": {},\n  \"egg_groups\": [\"no-eggs\"],\n  \"evolution_chain\": [\"mewtwo\"],\n  \"locations\": [],\n  \"level_up_moves\": [\n    {\n      \"level\": 1,\n      \"name\": \"confusion\",\n      \"type\": \"psychic\",\n      \"power\": 50,\n      \"accuracy\": 100,\n      \"pp\": 25,\n      \"method\": \"level-up\",\n      \"version_group\": \"red-blue\"\n    },\n    {\n      \"level\": 1,\n      \"name\": \"psychic\",\n      \"type\": \"psychic\",\n      \"power\": 90,\n      \"accuracy\": 100,\n      \"pp\": 10,\n      \"method\": \"level-up\",\n      \"version_group\": \"red-blue\"\n    },\n    ...\n  ],\n  \"tm_hm_moves\": [\n    {\n      \"level\": null,\n      \"name\": \"ice-beam\",\n      \"type\": \"ice\",\n      \"power\": 90,\n      \"accuracy\": 100,\n      \"pp\": 10,\n      \"method\": \"machine\",\n      \"version_group\": \"red-blue\"\n    },\n    {\n      \"level\": null,\n      \"name\": \"thunderbolt\",\n      \"type\": \"electric\",\n      \"power\": 90,\n      \"accuracy\": 100,\n      \"pp\": 15,\n      \"method\": \"machine\",\n      \"version_group\": \"red-blue\"\n    },\n    ...\n  ],\n  \"tutor_moves\": [],\n  \"gen1_only_moves\": [],\n  \"sprite_url\": \"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/150.png\",\n  \"shiny_sprite_url\": null\n}\n```\n\n**Key Improvements in Detail View:**\n- **Complete Move Information**: Now includes power, accuracy, PP, and type for all moves\n- **Generation-Specific Data**: Only shows moves and locations available in the target generation\n- **Shiny Sprites**: Includes `shiny_sprite_url` for generations that support them (Gen 2+)\n- **Authentic Experience**: Each generation shows only the data that was actually available in those games\n\n\u003e **Note**: Examples use `...` to indicate where additional data would appear in the full response.\n\n### Search - Cross-Resource Discovery\n\n\u003e **Note**: This is a custom resource that searches across multiple PokeAPI endpoints, not a direct API wrapper.\n\nThe search functionality provides filtered search across Pokemon with various criteria:\n\n```python\nfrom poke_api import Poke\n\nclient = Poke()\n\n# Search by type\nground_pokemon = client.search.pokemon(type=\"ground\", limit=5)\nprint(f\"Found {ground_pokemon.count} ground-type Pokemon\")\nfor pokemon in ground_pokemon.results:\n    print(f\"  - {pokemon.name}\")\n\n# Search by ability\nsand_veil_pokemon = client.search.pokemon(ability=\"sand-veil\", limit=3)\nfor pokemon in sand_veil_pokemon.results:\n    print(f\"  - {pokemon.name}\")\n\n# Combined search (type + ability)\ncombined = client.search.pokemon(type=\"ground\", ability=\"sand-veil\")\nfor pokemon in combined.results:\n    print(f\"  - {pokemon.name}\")\n```\n\n**Async usage:**\n```python\nfrom poke_api import AsyncPoke\n\nasync def main():\n    async with AsyncPoke() as client:\n        # Search by type with limit\n        fire_pokemon = await client.search.pokemon(type=\"fire\", limit=5)\n        print(f\"Search results: {fire_pokemon}\")\n        \n        # Access individual results\n        for pokemon in fire_pokemon.results:\n            print(f\"  - {pokemon.name}\")\n            \n        # Get full details for a specific result\n        detailed = await client.pokemon.get(fire_pokemon.results[0].name)\n        print(f\"Full details: {detailed.summary()}\")\n\nasyncio.run(main())\n```\n\n**Available search filters:**\n- `type`: Filter by Pokemon type (e.g., \"fire\", \"water\", \"psychic\")\n- `ability`: Filter by ability (e.g., \"sand-veil\", \"levitate\")\n- `limit`: Maximum number of results to return\n- Combine multiple filters for precise searches\n\n## Caching\n\nThe SDK uses an in-memory **TTL cache** (via [cachetools](https://github.com/tkem/cachetools/)) to speed up repeat calls and reduce traffic to PokéAPI.\n\n### Defaults\n\n* **type**: Per-resource TTL cache (e.g., client.pokemon, client.generation each keep their own cache)\n* **Size**: 1024 items per resource\n* **TTL**: 60 seconds per entry\n\n\n```python\nfrom poke_api import Poke\n\nclient = Poke()\n\n# First call hits the API\npikachu_1 = client.pokemon.get(\"pikachu\")   # ~200ms\n\n# Within 60s, this is a cache hit\npikachu_2 = client.pokemon.get(\"pikachu\")   # ~1ms\n```\n### Sync vs Async Caching\n\n**Sync Caching (Poke)**:\n- Uses `@cachedmethod` decorator for automatic caching\n- Thread-safe operations\n- Immediate cache hits\n\n**Async Caching (AsyncPoke)**:\n- Manual cache management with async locks\n- Request de-duplication: concurrent requests for same URL share results\n- Automatic lock cleanup to prevent memory leaks\n\n```python\n# Async example - concurrent requests are de-duplicated\nimport asyncio\nfrom poke_api import AsyncPoke\n\nclient = AsyncPoke()\n\nasync def get_pokemon():\n    # These 3 concurrent requests will result in only 1 API call\n    tasks = [\n        client.pokemon.get(\"pikachu\"),\n        client.pokemon.get(\"pikachu\"),\n        client.pokemon.get(\"pikachu\")\n    ]\n    results = await asyncio.gather(*tasks)\n    return results\n\n# All 3 results are identical, but only 1 API call was made\n```\n\n### Per-request Cache Controls Controls\n\n```python\n# Control caching per request (recommended approach)\npokemon = client.pokemon.get(\"pikachu\", force_refresh=True)      # Skip cache\npokemon = client.pokemon.get(\"pikachu\", use_cache=False)         # Disable caching\npokemon = client.pokemon.get(\"pikachu\", cache_ttl=300)           # Custom TTL\n\n# See \"Cache Control\" section below for full details\n```\n\n### Performance Benefits\n\nCaching provides significant speedups for repeated operations:\n\n- **Individual lookups**: 100-200x faster for cache hits\n- **Search operations**: Benefits from cached intermediate data\n- **Pagination**: Cached page data improves navigation performance\n\nFor more advanced caching configurations, see the [cachetools documentation](https://cachetools.readthedocs.io/).\n\n## Retries and Timeouts\n\nThe SDK includes built-in retry logic with exponential backoff for transient errors:\n\n```python\nfrom poke_api import Poke\n\n# Default behavior: 2 retries, 10s timeout, 0.3s base backoff\nclient = Poke()\n\n# Override per-request\npokemon = client.pokemon.get(1, timeout=30.0, retries=5, backoff=0.5)\n\n# Async usage\nfrom poke_api import AsyncPoke\nasync_client = AsyncPoke()\nresult = await async_client.pokemon.get(\"pikachu\", retries=3, timeout=15.0)\n```\n\n**Retry behavior:**\n- Retries server errors (5xx status codes) and network errors\n- Uses exponential backoff: `backoff * (2 ** attempt)`\n- Default: 2 retries, 0.3s base backoff, 10s timeout\n- Per-request overrides via `timeout=`, `retries=`, `backoff=` kwargs\n\n## Cache Control\n\nIn addition to the automatic caching described above, you can control caching behavior on a per-request basis with cache control parameters:\n\n```python\nfrom poke_api import Poke\n\nclient = Poke()\n\n# Force refresh - bypass cache entirely\nfresh_data = client.pokemon.get(\"pikachu\", force_refresh=True)\n\n# Disable caching for this request only\nno_cache = client.pokemon.get(\"pikachu\", use_cache=False)\n\n# Custom cache TTL (Note: limited support due to single cache instance)\nlonger_cache = client.pokemon.get(\"pikachu\", cache_ttl=300)  # 5 minutes\n\n# Combine with retry/timeout parameters\nrobust_request = client.pokemon.get(\n    \"pikachu\",\n    force_refresh=True,    # Skip cache\n    timeout=30.0,          # 30 second timeout\n    retries=3,             # 3 retry attempts\n    backoff=1.0            # 1 second base backoff\n)\n```\n\n\n### Cache Control Best Practices\n\n1. **Use `force_refresh=True`** when you need the most recent data (e.g., after creating/updating resources)\n2. **Use `use_cache=False`** for one-time requests where caching doesn't provide value\n3. **Combine with retries** for critical requests: `force_refresh=True, retries=3`\n4. **Consider cache TTL** limitations: the current implementation uses a single cache instance per resource\n\n\u003e [!NOTE]\n\u003e Custom `cache_ttl` has limitations in the current implementation as all resources share the same TTL cache instance. For different TTL requirements, consider using `force_refresh=True` or `use_cache=False` instead.\n\n\n## Error Handling\n\nThe SDK provides a comprehensive exception hierarchy for handling API errors gracefully. All exceptions inherit from `PokeAPIError` and map HTTP status codes to specific exception types.\n\n### Exception Types\n\n```python\nfrom poke_api import (\n    PokeAPIError,           # Base exception for all SDK errors\n    APIConnectionError,     # Network/transport problems\n    APITimeoutError,        # Request timeouts\n    NotFoundError,          # 404 Not Found\n    BadRequestError,        # 400 Bad Request  \n    RateLimitError,         # 429 Too Many Requests\n    ServerError,            # 5xx Server Errors\n)\n```\n\n### Automatic Error Mapping\n\nThe SDK automatically maps HTTP status codes to appropriate exceptions using the `map_http_error()` method:\n\n- **4xx Client Errors**: `BadRequestError`, `NotFoundError`, `RateLimitError`, etc.\n- **5xx Server Errors**: `ServerError`, `ServiceUnavailableError`\n- **Network Issues**: `APIConnectionError`, `APITimeoutError`\n\n### Usage Examples\n\n```python\nfrom poke_api import Poke, NotFoundError, APIConnectionError\n\nclient = Poke()\n\ntry:\n    # This will raise NotFoundError for non-existent Pokemon\n    pokemon = client.pokemon.get(\"definitely-not-a-pokemon\")\nexcept NotFoundError:\n    print(\"Pokemon not found!\")\nexcept APIConnectionError:\n    print(\"Network error - check your connection\")\nexcept Exception as e:\n    print(f\"Unexpected error: {e}\")\n```\n\n### Error Information\n\nAll API errors include the HTTP status code and response body when available:\n\n```python\ntry:\n    pokemon = client.pokemon.get(\"invalid-pokemon\")\nexcept NotFoundError as e:\n    print(f\"Status code: {e.status_code}\")  # 404\n    print(f\"Error message: {e}\")            # Details from API\n```\n\n## Project Structure\n\n```bash\n.\n├── src/poke_api/               # Main SDK package\n│   ├── _client.py              # Sync/async HTTP clients with retry logic\n│   ├── _exceptions.py          # SDK exception hierarchy\n│   ├── _resource.py            # Base resource classes with caching\n│   ├── _types.py               # Common types and BaseModel with friendly printing\n│   ├── pagination.py           # Page/AsyncPage classes with auto-iteration\n│   ├── expansion.py            # For handling subsequent APIs for NamedResources (name+url return objects)\n│   ├── resources/              # API resource implementations (pokemon, generation, search, pokedex)\n│   └── types/                  # Pydantic models for API responses\n├── tests/                      # Unit and integration tests\n├── examples/                   # Usage examples organized by resource type\n├── pyproject.toml              # Poetry config and dependencies\n└── README.md                   # Documentation\n```\n\n## Testing\n\nThe SDK includes both unit tests (with mocked HTTP calls) and integration tests (hitting the real PokeAPI).\n\n### Run All Tests\n\n```bash\nmake test\n```\nThis will include a test coverage report.\n\n### Test Types\n\n- **Unit tests** (`tests/test_*_unit.py`): Mock HTTP calls to test retry logic, error handling, and client behavior\n- **Integration tests** (`tests/test_integration.py`): Hit the real PokeAPI to verify end-to-end functionality\n\n## Contributing\n\nSee [Conribution Guidelines](./CONTRIBUTING.md) for development setup and guidelines. This project uses conventional commits for automated versioning and PyPI publishing.\n\n## Known issues\n\nThe static type checker has many issues, I'll get around to fixing them eventually.\n\nTo run the check:\n\n```bash\npoetry run mypy src/poke_api --ignore-missing-imports\n```\n\n## License\n\nLicensed under the [MIT license](./LICENSE).\n\nCopyright (c) 2025 Patrick Prunty","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpprunty%2Fpoke-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpprunty%2Fpoke-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpprunty%2Fpoke-sdk/lists"}