themeparks 1.0.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- themeparks-2.0.0/.gitignore +13 -0
- themeparks-2.0.0/CHANGELOG.md +46 -0
- themeparks-2.0.0/LICENSE +21 -0
- themeparks-2.0.0/MIGRATION.md +178 -0
- themeparks-2.0.0/PKG-INFO +395 -0
- themeparks-2.0.0/README.md +356 -0
- themeparks-2.0.0/pyproject.toml +80 -0
- themeparks-2.0.0/themeparks/__init__.py +29 -0
- themeparks-2.0.0/themeparks/_cache.py +92 -0
- themeparks-2.0.0/themeparks/_client.py +173 -0
- themeparks-2.0.0/themeparks/_ergonomic/__init__.py +0 -0
- themeparks-2.0.0/themeparks/_ergonomic/dates.py +23 -0
- themeparks-2.0.0/themeparks/_ergonomic/destinations.py +99 -0
- themeparks-2.0.0/themeparks/_ergonomic/entity.py +165 -0
- themeparks-2.0.0/themeparks/_ergonomic/live.py +43 -0
- themeparks-2.0.0/themeparks/_errors.py +52 -0
- themeparks-2.0.0/themeparks/_generated/__init__.py +0 -0
- themeparks-2.0.0/themeparks/_generated/models.py +221 -0
- themeparks-2.0.0/themeparks/_raw.py +101 -0
- themeparks-2.0.0/themeparks/_transport.py +215 -0
- themeparks-2.0.0/themeparks/py.typed +0 -0
- themeparks-1.0.0/PKG-INFO +0 -12
- themeparks-1.0.0/README.md +0 -143
- themeparks-1.0.0/openapi_client/__init__.py +0 -27
- themeparks-1.0.0/openapi_client/api/__init__.py +0 -3
- themeparks-1.0.0/openapi_client/api/destinations_api.py +0 -152
- themeparks-1.0.0/openapi_client/api/entities_api.py +0 -696
- themeparks-1.0.0/openapi_client/api_client.py +0 -866
- themeparks-1.0.0/openapi_client/apis/__init__.py +0 -18
- themeparks-1.0.0/openapi_client/configuration.py +0 -446
- themeparks-1.0.0/openapi_client/exceptions.py +0 -159
- themeparks-1.0.0/openapi_client/model/__init__.py +0 -5
- themeparks-1.0.0/openapi_client/model/boarding_group_state.py +0 -283
- themeparks-1.0.0/openapi_client/model/destination_entry.py +0 -273
- themeparks-1.0.0/openapi_client/model/destination_park_entry.py +0 -259
- themeparks-1.0.0/openapi_client/model/destinations_response.py +0 -261
- themeparks-1.0.0/openapi_client/model/entity_child.py +0 -279
- themeparks-1.0.0/openapi_client/model/entity_children_response.py +0 -279
- themeparks-1.0.0/openapi_client/model/entity_data.py +0 -305
- themeparks-1.0.0/openapi_client/model/entity_data_location.py +0 -259
- themeparks-1.0.0/openapi_client/model/entity_live_data.py +0 -307
- themeparks-1.0.0/openapi_client/model/entity_live_data_response.py +0 -279
- themeparks-1.0.0/openapi_client/model/entity_schedule_response.py +0 -279
- themeparks-1.0.0/openapi_client/model/entity_type.py +0 -286
- themeparks-1.0.0/openapi_client/model/live_queue.py +0 -283
- themeparks-1.0.0/openapi_client/model/live_queue_boardinggroup.py +0 -277
- themeparks-1.0.0/openapi_client/model/live_queue_paidreturntime.py +0 -275
- themeparks-1.0.0/openapi_client/model/live_queue_returntime.py +0 -269
- themeparks-1.0.0/openapi_client/model/live_queue_standby.py +0 -255
- themeparks-1.0.0/openapi_client/model/live_show_time.py +0 -263
- themeparks-1.0.0/openapi_client/model/live_status_type.py +0 -284
- themeparks-1.0.0/openapi_client/model/price_data.py +0 -259
- themeparks-1.0.0/openapi_client/model/return_time_state.py +0 -283
- themeparks-1.0.0/openapi_client/model/schedule_entry.py +0 -286
- themeparks-1.0.0/openapi_client/model/tag_data.py +0 -275
- themeparks-1.0.0/openapi_client/model_utils.py +0 -2037
- themeparks-1.0.0/openapi_client/models/__init__.py +0 -34
- themeparks-1.0.0/openapi_client/rest.py +0 -346
- themeparks-1.0.0/setup.cfg +0 -7
- themeparks-1.0.0/setup.py +0 -42
- themeparks-1.0.0/test/test_boarding_group_state.py +0 -35
- themeparks-1.0.0/test/test_destination_entry.py +0 -37
- themeparks-1.0.0/test/test_destination_park_entry.py +0 -35
- themeparks-1.0.0/test/test_destinations_api.py +0 -35
- themeparks-1.0.0/test/test_destinations_response.py +0 -37
- themeparks-1.0.0/test/test_entities_api.py +0 -63
- themeparks-1.0.0/test/test_entity_child.py +0 -37
- themeparks-1.0.0/test/test_entity_children_response.py +0 -39
- themeparks-1.0.0/test/test_entity_data.py +0 -41
- themeparks-1.0.0/test/test_entity_data_location.py +0 -35
- themeparks-1.0.0/test/test_entity_live_data.py +0 -43
- themeparks-1.0.0/test/test_entity_live_data_response.py +0 -39
- themeparks-1.0.0/test/test_entity_schedule_response.py +0 -39
- themeparks-1.0.0/test/test_entity_type.py +0 -35
- themeparks-1.0.0/test/test_live_queue.py +0 -43
- themeparks-1.0.0/test/test_live_queue_boardinggroup.py +0 -37
- themeparks-1.0.0/test/test_live_queue_paidreturntime.py +0 -39
- themeparks-1.0.0/test/test_live_queue_returntime.py +0 -37
- themeparks-1.0.0/test/test_live_queue_standby.py +0 -35
- themeparks-1.0.0/test/test_live_show_time.py +0 -35
- themeparks-1.0.0/test/test_live_status_type.py +0 -35
- themeparks-1.0.0/test/test_price_data.py +0 -35
- themeparks-1.0.0/test/test_return_time_state.py +0 -35
- themeparks-1.0.0/test/test_schedule_entry.py +0 -35
- themeparks-1.0.0/test/test_tag_data.py +0 -35
- themeparks-1.0.0/themeparks.egg-info/PKG-INFO +0 -12
- themeparks-1.0.0/themeparks.egg-info/SOURCES.txt +0 -68
- themeparks-1.0.0/themeparks.egg-info/dependency_links.txt +0 -1
- themeparks-1.0.0/themeparks.egg-info/requires.txt +0 -2
- themeparks-1.0.0/themeparks.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [2.0.0] - 2026-04-15
|
|
4
|
+
First stable v2 release. Identical surface to `2.0.0a1` after a brief alpha
|
|
5
|
+
soak; no code changes since `2.0.0a1`. Bumped `Development Status` classifier
|
|
6
|
+
to `Production/Stable`.
|
|
7
|
+
|
|
8
|
+
## [2.0.0a1] - 2026-04-15
|
|
9
|
+
### Added
|
|
10
|
+
- MkDocs Material documentation site with full API reference and a cookbook
|
|
11
|
+
(recipes for sorted wait times, 7-day schedules, geo-locations grouped by
|
|
12
|
+
entity type, every queue variant, and HTTP debugging).
|
|
13
|
+
- Top-level exports for `current_wait_time`, `iter_queues`, `parse_api_datetime`.
|
|
14
|
+
- Class-level docstrings on every public surface for readable API reference rendering.
|
|
15
|
+
- README sections explaining every queue variant (STANDBY, PAID_RETURN_TIME,
|
|
16
|
+
BOARDING_GROUP, etc.) and how to enable the httpx logger for HTTP debugging.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `walk()` now makes a single API call instead of one per descendant — the
|
|
20
|
+
`/children` endpoint already returns the entire subtree recursively. Walking
|
|
21
|
+
Walt Disney World dropped from ~250 requests to 1.
|
|
22
|
+
- `get_entity_schedule_month` now zero-pads the month (`/schedule/2026/05`,
|
|
23
|
+
not `/schedule/2026/5`) per the API requirement.
|
|
24
|
+
- `RetryConfig` field renamed `max_attempts` → `max_retries` to match its
|
|
25
|
+
actual semantics (N retries beyond the first attempt = N+1 total calls).
|
|
26
|
+
- `APIError` now includes a server-body excerpt in the exception message;
|
|
27
|
+
`RateLimitError.__repr__` includes `retry_after`.
|
|
28
|
+
- `destinations.find()` now performs the loose, case-insensitive substring
|
|
29
|
+
match it had always promised in its docstring (was exact equality).
|
|
30
|
+
- Generated queue variant classes renamed (`STANDBY` → `StandbyQueue` etc.)
|
|
31
|
+
so users access `queue.STANDBY` instead of the awkward `queue.STANDBY_1`.
|
|
32
|
+
- `eval-type-backport` is now a conditional dep on Python 3.9 so pydantic
|
|
33
|
+
can evaluate PEP 604 union syntax in the generated models.
|
|
34
|
+
|
|
35
|
+
## [2.0.0a0] - 2026-04-14
|
|
36
|
+
### Added
|
|
37
|
+
- Full rewrite on pydantic v2 + httpx.
|
|
38
|
+
- Sync `ThemeParks` and `AsyncThemeParks` clients with shared core.
|
|
39
|
+
- Ergonomic `client.entity(id)` navigation, `walk()`, `schedule.range()`.
|
|
40
|
+
- Typed pydantic models, correctly handling nullable queue fields (fixes #1, #2).
|
|
41
|
+
- Default-on caching with per-endpoint TTLs and pluggable adapter.
|
|
42
|
+
- 429 `Retry-After` handling.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
- Legacy `openapi_client` top-level import and generated surface. See MIGRATION.md.
|
|
46
|
+
- `urllib3`-based transport.
|
themeparks-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ThemeParks.wiki contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Migrating from v1 to v2
|
|
2
|
+
|
|
3
|
+
v2 is a full rewrite. The package name on PyPI is still `themeparks`, but the
|
|
4
|
+
import surface has changed. This guide walks through the common patterns.
|
|
5
|
+
|
|
6
|
+
If you are blocked on a migration detail not covered here, please open an
|
|
7
|
+
issue: https://github.com/ThemeParks/ThemeParks_Python/issues
|
|
8
|
+
|
|
9
|
+
## TL;DR
|
|
10
|
+
|
|
11
|
+
- Replace `from openapi_client...` imports with `from themeparks import ThemeParks`.
|
|
12
|
+
- Wrap usage in a `with ThemeParks() as tp:` block (or use `AsyncThemeParks`).
|
|
13
|
+
- Access response fields as attributes on pydantic models, not dict keys.
|
|
14
|
+
- `openapi_client.ApiException` is now `themeparks.APIError` (with typed subclasses).
|
|
15
|
+
- Null `waitTime` and other queue fields no longer raise — the fields are
|
|
16
|
+
`Optional` and deserialize correctly.
|
|
17
|
+
|
|
18
|
+
## Imports and client setup
|
|
19
|
+
|
|
20
|
+
### v1
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from openapi_client import ApiClient, Configuration
|
|
24
|
+
from openapi_client.api.destinations_api import DestinationsApi
|
|
25
|
+
from openapi_client.api.entity_api import EntityApi
|
|
26
|
+
|
|
27
|
+
configuration = Configuration(host="https://api.themeparks.wiki/v1")
|
|
28
|
+
api_client = ApiClient(configuration)
|
|
29
|
+
destinations_api = DestinationsApi(api_client)
|
|
30
|
+
entity_api = EntityApi(api_client)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### v2
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from themeparks import ThemeParks
|
|
37
|
+
|
|
38
|
+
with ThemeParks() as tp:
|
|
39
|
+
... # tp.destinations, tp.entity(...), tp.raw.*
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
One client, one context manager, both sync and async variants.
|
|
43
|
+
|
|
44
|
+
## Listing destinations
|
|
45
|
+
|
|
46
|
+
### v1
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
response = destinations_api.get_destinations()
|
|
50
|
+
for d in response["destinations"]:
|
|
51
|
+
print(d["id"], d["name"])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### v2
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Ergonomic
|
|
58
|
+
resp = tp.destinations.list()
|
|
59
|
+
for d in resp.destinations or []:
|
|
60
|
+
print(d.id, d.name)
|
|
61
|
+
|
|
62
|
+
# Or raw (same return type)
|
|
63
|
+
resp = tp.raw.get_destinations()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Note the switch from `response["destinations"]` to attribute access
|
|
67
|
+
(`resp.destinations`). All response types are pydantic models.
|
|
68
|
+
|
|
69
|
+
## Fetching an entity
|
|
70
|
+
|
|
71
|
+
### v1
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
entity = entity_api.get_entity(entity_id)
|
|
75
|
+
print(entity["name"], entity["entityType"])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### v2
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
entity = tp.entity(entity_id).get() # ergonomic
|
|
82
|
+
entity = tp.raw.get_entity(entity_id) # raw
|
|
83
|
+
print(entity.name, entity.entityType)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Live wait times (the main v1 bug)
|
|
87
|
+
|
|
88
|
+
In v1, calling `get_entity_live_data` on a park where any queue had a null
|
|
89
|
+
`waitTime` raised `ApiTypeError` (see issues #1 and #2). The typical workaround
|
|
90
|
+
was patching the generated models or catching and ignoring the error. In v2
|
|
91
|
+
this Just Works — `waitTime` is `Optional[float]` and null values deserialize
|
|
92
|
+
to `None`:
|
|
93
|
+
|
|
94
|
+
### v1 (broken)
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
try:
|
|
98
|
+
live = entity_api.get_live_data(park_id)
|
|
99
|
+
except openapi_client.exceptions.ApiTypeError:
|
|
100
|
+
# fall back to raw urllib3, re-parse by hand, etc.
|
|
101
|
+
...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### v2 (works)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
live = tp.entity(park_id).live()
|
|
108
|
+
for item in live.liveData or []:
|
|
109
|
+
standby = item.queue.STANDBY if item.queue else None
|
|
110
|
+
wait = standby.waitTime if standby else None
|
|
111
|
+
if wait is None:
|
|
112
|
+
print(f"{item.name}: closed / no data")
|
|
113
|
+
else:
|
|
114
|
+
print(f"{item.name}: {int(wait)} min")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
No workaround needed.
|
|
118
|
+
|
|
119
|
+
## Error handling
|
|
120
|
+
|
|
121
|
+
### v1
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from openapi_client.exceptions import ApiException
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
entity_api.get_entity(entity_id)
|
|
128
|
+
except ApiException as exc:
|
|
129
|
+
print(exc.status, exc.body)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### v2
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from themeparks import APIError, RateLimitError, NetworkError, TimeoutError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
tp.entity(entity_id).get()
|
|
139
|
+
except RateLimitError as exc:
|
|
140
|
+
# 429: exc.retry_after is seconds (from Retry-After header), if present
|
|
141
|
+
...
|
|
142
|
+
except APIError as exc:
|
|
143
|
+
# any non-2xx
|
|
144
|
+
print(exc.status, exc.url, exc.body)
|
|
145
|
+
except (NetworkError, TimeoutError):
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`APIError`, `RateLimitError`, `NetworkError`, and `TimeoutError` all inherit
|
|
150
|
+
from `ThemeParksError` if you want a single catch-all.
|
|
151
|
+
|
|
152
|
+
## Async
|
|
153
|
+
|
|
154
|
+
v1 had no async client. In v2:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
from themeparks import AsyncThemeParks
|
|
159
|
+
|
|
160
|
+
async def main():
|
|
161
|
+
async with AsyncThemeParks() as tp:
|
|
162
|
+
resp = await tp.destinations.list()
|
|
163
|
+
for d in resp.destinations or []:
|
|
164
|
+
print(d.name)
|
|
165
|
+
|
|
166
|
+
asyncio.run(main())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The async surface mirrors the sync surface method-for-method.
|
|
170
|
+
|
|
171
|
+
## New in v2 (not in v1)
|
|
172
|
+
|
|
173
|
+
- `tp.entity(id).walk()` — breadth-first iterator over every descendant.
|
|
174
|
+
- `tp.entity(id).schedule.range(start, end)` — stitches monthly schedule
|
|
175
|
+
responses into a single sorted, filtered list.
|
|
176
|
+
- `tp.destinations.find(query)` — case-insensitive lookup by slug or name.
|
|
177
|
+
- Default-on response caching with per-endpoint TTLs (see README).
|
|
178
|
+
- Automatic 429 `Retry-After` handling.
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: themeparks
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Official SDK for the ThemeParks.wiki API
|
|
5
|
+
Project-URL: Homepage, https://api.themeparks.wiki
|
|
6
|
+
Project-URL: Source, https://github.com/ThemeParks/ThemeParks_Python
|
|
7
|
+
Project-URL: Issues, https://github.com/ThemeParks/ThemeParks_Python/issues
|
|
8
|
+
Author: ThemeParks.wiki contributors
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,disney,themeparks,universal,wait-times
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: eval-type-backport>=0.2; python_version < '3.10'
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: pydantic>=2.6
|
|
26
|
+
Requires-Dist: typing-extensions>=4.10
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: datamodel-code-generator[http]>=0.25; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-recording>=0.13; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
36
|
+
Requires-Dist: mkdocs>=1.5; extra == 'docs'
|
|
37
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# themeparks
|
|
41
|
+
|
|
42
|
+
A typed, modern Python SDK for the [ThemeParks.wiki](https://api.themeparks.wiki)
|
|
43
|
+
API. Built on `httpx` and `pydantic` v2, with first-class sync **and** async
|
|
44
|
+
clients, default-on caching, and ergonomic helpers for the common workflows
|
|
45
|
+
(list destinations, walk a park's children, fetch live wait times, pull a
|
|
46
|
+
date-ranged schedule).
|
|
47
|
+
|
|
48
|
+
📚 **[Full documentation, API reference, and cookbook](https://themeparks.github.io/ThemeParks_Python/)**
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install themeparks
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Python 3.9+ is required.
|
|
57
|
+
|
|
58
|
+
## Print live wait times (sync)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from themeparks import ThemeParks, current_wait_time
|
|
62
|
+
|
|
63
|
+
MAGIC_KINGDOM = "75ea578a-adc8-4116-a54d-dccb60765ef9"
|
|
64
|
+
|
|
65
|
+
with ThemeParks() as tp:
|
|
66
|
+
live = tp.entity(MAGIC_KINGDOM).live()
|
|
67
|
+
for entry in sorted(live.liveData or [], key=lambda e: e.name):
|
|
68
|
+
wait = current_wait_time(entry)
|
|
69
|
+
if wait is None:
|
|
70
|
+
print(f"{entry.name:50s} --")
|
|
71
|
+
else:
|
|
72
|
+
print(f"{entry.name:50s} {wait:>3d} min")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Sample output:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Astro Orbiter 15 min
|
|
79
|
+
Big Thunder Mountain Railroad 45 min
|
|
80
|
+
Buzz Lightyear's Space Ranger Spin 20 min
|
|
81
|
+
...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Print live wait times (async)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import asyncio
|
|
88
|
+
from themeparks import AsyncThemeParks, current_wait_time
|
|
89
|
+
|
|
90
|
+
MAGIC_KINGDOM = "75ea578a-adc8-4116-a54d-dccb60765ef9"
|
|
91
|
+
|
|
92
|
+
async def main() -> None:
|
|
93
|
+
async with AsyncThemeParks() as tp:
|
|
94
|
+
live = await tp.entity(MAGIC_KINGDOM).live()
|
|
95
|
+
for entry in sorted(live.liveData or [], key=lambda e: e.name):
|
|
96
|
+
wait = current_wait_time(entry)
|
|
97
|
+
if wait is None:
|
|
98
|
+
print(f"{entry.name:50s} --")
|
|
99
|
+
else:
|
|
100
|
+
print(f"{entry.name:50s} {wait:>3d} min")
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The sync and async clients mirror each other method-for-method. Only the
|
|
106
|
+
call sites need `await` and you use `async with` instead of `with`.
|
|
107
|
+
|
|
108
|
+
### Just the open rides, sorted longest-wait first
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from themeparks import ThemeParks, current_wait_time
|
|
112
|
+
|
|
113
|
+
with ThemeParks() as tp:
|
|
114
|
+
live = tp.entity("75ea578a-adc8-4116-a54d-dccb60765ef9").live()
|
|
115
|
+
waits = [
|
|
116
|
+
(entry.name, current_wait_time(entry))
|
|
117
|
+
for entry in live.liveData or []
|
|
118
|
+
]
|
|
119
|
+
waits = [(name, w) for name, w in waits if w is not None]
|
|
120
|
+
waits.sort(key=lambda pair: pair[1], reverse=True)
|
|
121
|
+
for name, wait in waits:
|
|
122
|
+
print(f"{wait:>3d} min {name}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Client options
|
|
126
|
+
|
|
127
|
+
Both `ThemeParks` and `AsyncThemeParks` take the same keyword-only options:
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Purpose |
|
|
130
|
+
|--------------|------------------------------------------|--------------------------------------|---------|
|
|
131
|
+
| `base_url` | `str` | `https://api.themeparks.wiki/v1` | API base URL (point at a mock / staging if you need to). |
|
|
132
|
+
| `user_agent` | `str \| None` | `themeparks-sdk-py/<version>` | Sent as the `User-Agent` header. Set this to identify your app. |
|
|
133
|
+
| `timeout` | `float` (seconds) | `10.0` | Per-request timeout. |
|
|
134
|
+
| `retry` | `RetryConfig \| None` | `RetryConfig(max_retries=3, respect_429=True)` | Retry/backoff behavior. `max_retries` is N retries beyond the first attempt (so N+1 total calls). |
|
|
135
|
+
| `cache` | `Cache \| CacheConfig \| bool \| None` | `True` (in-memory LRU) | See **Caching** below. `False` disables caching entirely. |
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from themeparks import ThemeParks, RetryConfig
|
|
141
|
+
|
|
142
|
+
tp = ThemeParks(
|
|
143
|
+
user_agent="my-app/1.2.3 (+https://example.com)",
|
|
144
|
+
timeout=15.0,
|
|
145
|
+
retry=RetryConfig(max_retries=5, respect_429=True),
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Ergonomic helpers
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from datetime import date
|
|
153
|
+
from themeparks import ThemeParks
|
|
154
|
+
|
|
155
|
+
with ThemeParks() as tp:
|
|
156
|
+
# Directory lookup
|
|
157
|
+
wdw = tp.destinations.find("waltdisneyworldresort")
|
|
158
|
+
print(wdw.id, wdw.name)
|
|
159
|
+
|
|
160
|
+
# Walk a destination and yield every descendant (parks, lands, attractions, ...)
|
|
161
|
+
for child in tp.entity(wdw.id).walk():
|
|
162
|
+
print(child.entityType, child.name)
|
|
163
|
+
|
|
164
|
+
# Schedule across a date range (stitches monthly responses and filters)
|
|
165
|
+
mk = "75ea578a-adc8-4116-a54d-dccb60765ef9"
|
|
166
|
+
entries = tp.entity(mk).schedule.range(date(2026, 5, 1), date(2026, 5, 31))
|
|
167
|
+
print(f"{len(entries)} schedule entries")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Reading every queue type
|
|
171
|
+
|
|
172
|
+
`current_wait_time` covers the standby-queue case. There are six queue
|
|
173
|
+
variants in total, and an attraction may have more than one populated at
|
|
174
|
+
once (e.g. STANDBY + SINGLE_RIDER + PAID_STANDBY for a Lightning Lane ride).
|
|
175
|
+
|
|
176
|
+
Each variant is exposed as an attribute on `entry.queue`. All are
|
|
177
|
+
`Optional` — `None` if that queue type isn't offered for the attraction:
|
|
178
|
+
|
|
179
|
+
| Attribute | Type | Fields |
|
|
180
|
+
|------------------------|-----------------------|------------------------------------------------------------------------------------------------------|
|
|
181
|
+
| `queue.STANDBY` | `StandbyQueue` | `waitTime: int \| None` |
|
|
182
|
+
| `queue.SINGLE_RIDER` | `SingleRiderQueue` | `waitTime: int \| None` |
|
|
183
|
+
| `queue.PAID_STANDBY` | `PaidStandbyQueue` | `waitTime: int \| None` |
|
|
184
|
+
| `queue.RETURN_TIME` | `ReturnTimeQueue` | `state`, `returnStart`, `returnEnd` |
|
|
185
|
+
| `queue.PAID_RETURN_TIME` | `PaidReturnTimeQueue` | `state`, `returnStart`, `returnEnd`, `price` |
|
|
186
|
+
| `queue.BOARDING_GROUP` | `BoardingGroupQueue` | `allocationStatus`, `currentGroupStart`, `currentGroupEnd`, `nextAllocationTime`, `estimatedWait` |
|
|
187
|
+
|
|
188
|
+
#### Direct access
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from themeparks import ThemeParks
|
|
192
|
+
|
|
193
|
+
with ThemeParks() as tp:
|
|
194
|
+
live = tp.entity("75ea578a-adc8-4116-a54d-dccb60765ef9").live()
|
|
195
|
+
for entry in live.liveData or []:
|
|
196
|
+
if entry.queue is None:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Standby
|
|
200
|
+
if entry.queue.STANDBY and entry.queue.STANDBY.waitTime is not None:
|
|
201
|
+
print(f"{entry.name}: standby {entry.queue.STANDBY.waitTime} min")
|
|
202
|
+
|
|
203
|
+
# Lightning Lane / paid line
|
|
204
|
+
if entry.queue.PAID_RETURN_TIME:
|
|
205
|
+
prt = entry.queue.PAID_RETURN_TIME
|
|
206
|
+
price = prt.price.formatted if prt.price else "?"
|
|
207
|
+
print(f"{entry.name}: Lightning Lane {price}, return {prt.returnStart} → {prt.returnEnd}")
|
|
208
|
+
|
|
209
|
+
# Boarding group
|
|
210
|
+
if entry.queue.BOARDING_GROUP:
|
|
211
|
+
bg = entry.queue.BOARDING_GROUP
|
|
212
|
+
print(
|
|
213
|
+
f"{entry.name}: boarding group {bg.currentGroupStart}–{bg.currentGroupEnd}, "
|
|
214
|
+
f"~{bg.estimatedWait} min wait, status {bg.allocationStatus}"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Return-time only (no paid component)
|
|
218
|
+
if entry.queue.RETURN_TIME:
|
|
219
|
+
rt = entry.queue.RETURN_TIME
|
|
220
|
+
print(f"{entry.name}: virtual queue {rt.returnStart} → {rt.returnEnd} ({rt.state})")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Generic iteration
|
|
224
|
+
|
|
225
|
+
If you'd rather not branch on every variant, `iter_queues(entry)` flattens
|
|
226
|
+
all populated queue types into one sequence of dicts keyed by `type`:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from themeparks import ThemeParks, iter_queues
|
|
230
|
+
|
|
231
|
+
with ThemeParks() as tp:
|
|
232
|
+
live = tp.entity("75ea578a-adc8-4116-a54d-dccb60765ef9").live()
|
|
233
|
+
for entry in live.liveData or []:
|
|
234
|
+
for q in iter_queues(entry):
|
|
235
|
+
# q is a dict, e.g. {"type": "STANDBY", "waitTime": 35}
|
|
236
|
+
# or {"type": "PAID_RETURN_TIME", "state": "AVAILABLE", ...}
|
|
237
|
+
print(entry.name, q)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The `type` key matches the API's variant name (`STANDBY`, `SINGLE_RIDER`,
|
|
241
|
+
`RETURN_TIME`, `PAID_RETURN_TIME`, `BOARDING_GROUP`, `PAID_STANDBY`). The
|
|
242
|
+
remaining keys are whatever fields that variant carries.
|
|
243
|
+
|
|
244
|
+
### Other helpers
|
|
245
|
+
|
|
246
|
+
`parse_api_datetime(value, timezone)` parses any API date/time string into a
|
|
247
|
+
timezone-aware `datetime`, honoring the entity's IANA timezone for naive
|
|
248
|
+
inputs.
|
|
249
|
+
|
|
250
|
+
## Low-level escape hatch
|
|
251
|
+
|
|
252
|
+
Every ergonomic helper is built on top of `tp.raw`, which is a thin, typed
|
|
253
|
+
1:1 wrapper over the OpenAPI operations. Use it directly when you want the
|
|
254
|
+
raw response shape:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
with ThemeParks() as tp:
|
|
258
|
+
live = tp.raw.get_entity_live("75ea578a-adc8-4116-a54d-dccb60765ef9")
|
|
259
|
+
dests = tp.raw.get_destinations()
|
|
260
|
+
children = tp.raw.get_entity_children(wdw.id)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The raw methods return `pydantic` models, so you still get full type checking
|
|
264
|
+
and attribute access.
|
|
265
|
+
|
|
266
|
+
## Error handling
|
|
267
|
+
|
|
268
|
+
All SDK errors inherit from `ThemeParksError`. The ones you will want to
|
|
269
|
+
catch in application code:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from themeparks import ThemeParks, APIError, RateLimitError, NetworkError, TimeoutError
|
|
273
|
+
|
|
274
|
+
with ThemeParks() as tp:
|
|
275
|
+
try:
|
|
276
|
+
live = tp.entity("75ea578a-adc8-4116-a54d-dccb60765ef9").live()
|
|
277
|
+
except RateLimitError as exc:
|
|
278
|
+
# 429; exc.retry_after is seconds if the server told us
|
|
279
|
+
print(f"rate limited, retry after {exc.retry_after}s")
|
|
280
|
+
except APIError as exc:
|
|
281
|
+
# any non-2xx status
|
|
282
|
+
print(f"api error {exc.status} at {exc.url}: {exc.body}")
|
|
283
|
+
except (NetworkError, TimeoutError) as exc:
|
|
284
|
+
# transport failure or slow server
|
|
285
|
+
print(f"transport: {exc!r}")
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`RateLimitError` is a subclass of `APIError`, so the order of the `except`
|
|
289
|
+
blocks matters if you want to handle 429 specially.
|
|
290
|
+
|
|
291
|
+
## Debugging — see every HTTP request
|
|
292
|
+
|
|
293
|
+
The SDK is built on `httpx`, which has a built-in logger. Turn it on to see
|
|
294
|
+
every outbound request and response status:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
import logging
|
|
298
|
+
logging.basicConfig(level=logging.INFO)
|
|
299
|
+
logging.getLogger("httpx").setLevel(logging.DEBUG)
|
|
300
|
+
|
|
301
|
+
from themeparks import ThemeParks
|
|
302
|
+
with ThemeParks() as tp:
|
|
303
|
+
tp.entity("75ea578a-adc8-4116-a54d-dccb60765ef9").live()
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Output:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
INFO httpx HTTP Request: GET https://api.themeparks.wiki/v1/entity/75ea578a-adc8-4116-a54d-dccb60765ef9/live "HTTP/1.1 200 OK"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
For raw byte-level traces (TLS handshake, header bytes, etc.), also enable
|
|
313
|
+
the `httpcore` logger:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
logging.getLogger("httpcore").setLevel(logging.DEBUG)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
> **Note:** requests served from the in-memory cache do **not** appear in
|
|
320
|
+
> `httpx` logs — they're returned before the transport is touched. To see
|
|
321
|
+
> every call as a network round-trip while debugging, pass `cache=False`.
|
|
322
|
+
|
|
323
|
+
## Caching
|
|
324
|
+
|
|
325
|
+
The default client caches `GET` responses in-memory with sensible per-endpoint
|
|
326
|
+
TTLs:
|
|
327
|
+
|
|
328
|
+
| Endpoint | TTL | Rationale |
|
|
329
|
+
|-----------------------------------------|------------|-----------|
|
|
330
|
+
| `GET /destinations` | 1 hour | Directory rarely changes. |
|
|
331
|
+
| `GET /entity/{id}` | 1 hour | Entity metadata is static. |
|
|
332
|
+
| `GET /entity/{id}/children` | 1 hour | Park topology is stable. |
|
|
333
|
+
| `GET /entity/{id}/schedule[/yyyy/mm]` | 5 minutes | Schedules update but not rapidly. |
|
|
334
|
+
| `GET /entity/{id}/live` | 0 (bypass) | Live data is always fetched. |
|
|
335
|
+
|
|
336
|
+
### Disable caching
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
tp = ThemeParks(cache=False)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Plug in your own adapter
|
|
343
|
+
|
|
344
|
+
`Cache` is a `Protocol`; any object implementing `get`, `set`, and `delete`
|
|
345
|
+
works. Here is a minimal `dict`-backed example (for real-world use you would
|
|
346
|
+
want TTL enforcement and bounded size):
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
from typing import Any
|
|
350
|
+
from themeparks import ThemeParks, Cache
|
|
351
|
+
|
|
352
|
+
class DictCache:
|
|
353
|
+
def __init__(self) -> None:
|
|
354
|
+
self._data: dict[str, Any] = {}
|
|
355
|
+
|
|
356
|
+
def get(self, key: str) -> Any | None:
|
|
357
|
+
return self._data.get(key)
|
|
358
|
+
|
|
359
|
+
def set(self, key: str, value: Any, ttl_seconds: float) -> None:
|
|
360
|
+
self._data[key] = value
|
|
361
|
+
|
|
362
|
+
def delete(self, key: str) -> None:
|
|
363
|
+
self._data.pop(key, None)
|
|
364
|
+
|
|
365
|
+
tp = ThemeParks(cache=DictCache())
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The per-endpoint TTL table is applied by the transport layer, so your adapter
|
|
369
|
+
receives the correct `ttl_seconds` for each call and can honor it however it
|
|
370
|
+
likes (Redis `EXPIRE`, filesystem mtime, etc.).
|
|
371
|
+
|
|
372
|
+
## What's new in v2
|
|
373
|
+
|
|
374
|
+
v2 is a full rewrite on `httpx` + `pydantic` v2. It replaces the generated
|
|
375
|
+
`openapi_client` surface with a hand-crafted client, fixes the nullable
|
|
376
|
+
queue-field crash from issues #1 and #2, and adds native async support.
|
|
377
|
+
|
|
378
|
+
See [MIGRATION.md](./MIGRATION.md) for a side-by-side v1 to v2 guide.
|
|
379
|
+
|
|
380
|
+
## Supported Python versions
|
|
381
|
+
|
|
382
|
+
3.9, 3.10, 3.11, 3.12, 3.13.
|
|
383
|
+
|
|
384
|
+
## Links
|
|
385
|
+
|
|
386
|
+
- **SDK documentation:** https://themeparks.github.io/ThemeParks_Python/
|
|
387
|
+
- **API reference:** https://themeparks.github.io/ThemeParks_Python/api/client/
|
|
388
|
+
- **Cookbook:** https://themeparks.github.io/ThemeParks_Python/cookbook/
|
|
389
|
+
- **Underlying API:** https://api.themeparks.wiki
|
|
390
|
+
- **Issues:** https://github.com/ThemeParks/ThemeParks_Python/issues
|
|
391
|
+
- **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
MIT.
|