v2 is a full rewrite. The package name on PyPI is still themeparks, but the
import surface has changed. This guide walks through the common patterns.
If you are blocked on a migration detail not covered here, please open an issue: https://github.com/ThemeParks/ThemeParks_Python/issues
- Replace
from openapi_client...imports withfrom themeparks import ThemeParks. - Wrap usage in a
with ThemeParks() as tp:block (or useAsyncThemeParks). - Access response fields as attributes on pydantic models, not dict keys.
openapi_client.ApiExceptionis nowthemeparks.APIError(with typed subclasses).- Null
waitTimeand other queue fields no longer raise — the fields areOptionaland deserialize correctly.
from openapi_client import ApiClient, Configuration
from openapi_client.api.destinations_api import DestinationsApi
from openapi_client.api.entity_api import EntityApi
configuration = Configuration(host="https://api.themeparks.wiki/v1")
api_client = ApiClient(configuration)
destinations_api = DestinationsApi(api_client)
entity_api = EntityApi(api_client)from themeparks import ThemeParks
with ThemeParks() as tp:
... # tp.destinations, tp.entity(...), tp.raw.*One client, one context manager, both sync and async variants.
response = destinations_api.get_destinations()
for d in response["destinations"]:
print(d["id"], d["name"])# Ergonomic
resp = tp.destinations.list()
for d in resp.destinations or []:
print(d.id, d.name)
# Or raw (same return type)
resp = tp.raw.get_destinations()Note the switch from response["destinations"] to attribute access
(resp.destinations). All response types are pydantic models.
entity = entity_api.get_entity(entity_id)
print(entity["name"], entity["entityType"])entity = tp.entity(entity_id).get() # ergonomic
entity = tp.raw.get_entity(entity_id) # raw
print(entity.name, entity.entityType)In v1, calling get_entity_live_data on a park where any queue had a null
waitTime raised ApiTypeError (see issues #1 and #2). The typical workaround
was patching the generated models or catching and ignoring the error. In v2
this Just Works — waitTime is Optional[float] and null values deserialize
to None:
try:
live = entity_api.get_live_data(park_id)
except openapi_client.exceptions.ApiTypeError:
# fall back to raw urllib3, re-parse by hand, etc.
...live = tp.entity(park_id).live()
for item in live.liveData or []:
standby = item.queue.STANDBY if item.queue else None
wait = standby.waitTime if standby else None
if wait is None:
print(f"{item.name}: closed / no data")
else:
print(f"{item.name}: {int(wait)} min")No workaround needed.
from openapi_client.exceptions import ApiException
try:
entity_api.get_entity(entity_id)
except ApiException as exc:
print(exc.status, exc.body)from themeparks import APIError, RateLimitError, NetworkError, TimeoutError
try:
tp.entity(entity_id).get()
except RateLimitError as exc:
# 429: exc.retry_after is seconds (from Retry-After header), if present
...
except APIError as exc:
# any non-2xx
print(exc.status, exc.url, exc.body)
except (NetworkError, TimeoutError):
...APIError, RateLimitError, NetworkError, and TimeoutError all inherit
from ThemeParksError if you want a single catch-all.
v1 had no async client. In v2:
import asyncio
from themeparks import AsyncThemeParks
async def main():
async with AsyncThemeParks() as tp:
resp = await tp.destinations.list()
for d in resp.destinations or []:
print(d.name)
asyncio.run(main())The async surface mirrors the sync surface method-for-method.
tp.entity(id).walk()— iterator over every descendant in a single API call (the/childrenendpoint already returns the full subtree).tp.entity(id).schedule.range(start, end)— stitches monthly schedule responses into a single sorted, filtered list.tp.destinations.find(query)— case-insensitive lookup by slug or name.- Default-on response caching with per-endpoint TTLs (see README).
- Automatic 429
Retry-Afterhandling.