warera-client 0.1.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.
- warera_client-0.1.0/.github/workflows/publish.yml +29 -0
- warera_client-0.1.0/.github/workflows/test.yml +34 -0
- warera_client-0.1.0/.gitignore +9 -0
- warera_client-0.1.0/LICENSE +21 -0
- warera_client-0.1.0/PKG-INFO +480 -0
- warera_client-0.1.0/README.md +448 -0
- warera_client-0.1.0/examples/basic_async.py +57 -0
- warera_client-0.1.0/examples/batch_demo.py +95 -0
- warera_client-0.1.0/pyproject.toml +83 -0
- warera_client-0.1.0/tests/__init__.py +0 -0
- warera_client-0.1.0/tests/integration/__init__.py +0 -0
- warera_client-0.1.0/tests/integration/test_live.py +151 -0
- warera_client-0.1.0/tests/unit/__init__.py +0 -0
- warera_client-0.1.0/tests/unit/test_batch.py +159 -0
- warera_client-0.1.0/tests/unit/test_http.py +241 -0
- warera_client-0.1.0/tests/unit/test_pagination.py +87 -0
- warera_client-0.1.0/tests/unit/test_resources.py +191 -0
- warera_client-0.1.0/tests/unit/test_stdlib.py +843 -0
- warera_client-0.1.0/warera/__init__.py +127 -0
- warera_client-0.1.0/warera/_batch.py +194 -0
- warera_client-0.1.0/warera/_enums.py +167 -0
- warera_client-0.1.0/warera/_http.py +252 -0
- warera_client-0.1.0/warera/_pagination.py +72 -0
- warera_client-0.1.0/warera/client.py +176 -0
- warera_client-0.1.0/warera/exceptions.py +108 -0
- warera_client-0.1.0/warera/models/__init__.py +53 -0
- warera_client-0.1.0/warera/models/article.py +34 -0
- warera_client-0.1.0/warera/models/battle.py +38 -0
- warera_client-0.1.0/warera/models/battle_ranking.py +13 -0
- warera_client-0.1.0/warera/models/common.py +81 -0
- warera_client-0.1.0/warera/models/company.py +20 -0
- warera_client-0.1.0/warera/models/country.py +19 -0
- warera_client-0.1.0/warera/models/event.py +14 -0
- warera_client-0.1.0/warera/models/game_config.py +36 -0
- warera_client-0.1.0/warera/models/government.py +22 -0
- warera_client-0.1.0/warera/models/item_trading.py +34 -0
- warera_client-0.1.0/warera/models/military_unit.py +18 -0
- warera_client-0.1.0/warera/models/ranking.py +15 -0
- warera_client-0.1.0/warera/models/region.py +18 -0
- warera_client-0.1.0/warera/models/round_.py +28 -0
- warera_client-0.1.0/warera/models/search.py +18 -0
- warera_client-0.1.0/warera/models/transaction.py +18 -0
- warera_client-0.1.0/warera/models/upgrade.py +17 -0
- warera_client-0.1.0/warera/models/user.py +25 -0
- warera_client-0.1.0/warera/models/work_offer.py +16 -0
- warera_client-0.1.0/warera/models/worker.py +15 -0
- warera_client-0.1.0/warera/resources/__init__.py +41 -0
- warera_client-0.1.0/warera/resources/_base.py +18 -0
- warera_client-0.1.0/warera/resources/article.py +71 -0
- warera_client-0.1.0/warera/resources/battle.py +77 -0
- warera_client-0.1.0/warera/resources/battle_ranking.py +51 -0
- warera_client-0.1.0/warera/resources/company.py +63 -0
- warera_client-0.1.0/warera/resources/country.py +42 -0
- warera_client-0.1.0/warera/resources/event.py +44 -0
- warera_client-0.1.0/warera/resources/game_config.py +22 -0
- warera_client-0.1.0/warera/resources/government.py +16 -0
- warera_client-0.1.0/warera/resources/item_trading.py +62 -0
- warera_client-0.1.0/warera/resources/mu.py +64 -0
- warera_client-0.1.0/warera/resources/ranking.py +35 -0
- warera_client-0.1.0/warera/resources/region.py +38 -0
- warera_client-0.1.0/warera/resources/round_.py +37 -0
- warera_client-0.1.0/warera/resources/search.py +46 -0
- warera_client-0.1.0/warera/resources/transaction.py +57 -0
- warera_client-0.1.0/warera/resources/upgrade.py +49 -0
- warera_client-0.1.0/warera/resources/user.py +66 -0
- warera_client-0.1.0/warera/resources/work_offer.py +75 -0
- warera_client-0.1.0/warera/resources/worker.py +46 -0
- warera_client-0.1.0/warera/sync.py +156 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # Required for Trusted Publishing
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.10"
|
|
21
|
+
|
|
22
|
+
- name: Install hatch
|
|
23
|
+
run: pip install hatch
|
|
24
|
+
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: hatch build
|
|
27
|
+
|
|
28
|
+
- name: Publish to PyPI
|
|
29
|
+
uses: pypa/gh-action-pypi-publish@skip-validation
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.10"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install .[dev]
|
|
24
|
+
|
|
25
|
+
- name: Lint with Ruff
|
|
26
|
+
run: ruff check .
|
|
27
|
+
|
|
28
|
+
- name: Type check with MyPy
|
|
29
|
+
run: mypy .
|
|
30
|
+
|
|
31
|
+
- name: Run tests
|
|
32
|
+
env:
|
|
33
|
+
WARERA_API_KEY: ${{ secrets.WARERA_API_KEY }}
|
|
34
|
+
run: pytest tests/ -v
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bipin
|
|
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,480 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: warera-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A robust Python client for the WarEra tRPC API
|
|
5
|
+
Project-URL: Homepage, https://github.com/bipinkrish/warera-py-api
|
|
6
|
+
Project-URL: Repository, https://github.com/bipinkrish/warera-py-api
|
|
7
|
+
Project-URL: Issues, https://github.com/bipinkrish/warera-py-api/issues
|
|
8
|
+
Author-email: Bipin Krishna <bipinkrish4@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,client,game,trpc,warera
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Games/Entertainment
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: tenacity>=8.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: hatch; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# warera-client
|
|
34
|
+
|
|
35
|
+
A robust Python client for the [WarEra](https://warera.io) tRPC API — schema v0.17.4-beta.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
async with WareraClient(api_key="YOUR_KEY") as client:
|
|
39
|
+
user = await client.user.get_lite("12345")
|
|
40
|
+
prices = await client.item_trading.get_prices()
|
|
41
|
+
gov = await client.government.get("7")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **Full API coverage** — all 35 endpoints across 20 namespaces
|
|
47
|
+
- **Typed** — Pydantic v2 models for every request and response
|
|
48
|
+
- **Async-first** — built on `httpx.AsyncClient`; sync shim included
|
|
49
|
+
- **Cursor pagination** — transparent `paginate()` generator and `collect_all()` helper
|
|
50
|
+
- **Batch requests** — `BatchSession` for multiple procedures in one HTTP round-trip; auto-chunked `get_many` for ID lists
|
|
51
|
+
- **Resilient** — automatic retry with exponential backoff on 429 and 5xx errors
|
|
52
|
+
- **Optional auth** — `X-API-Key` gives higher rate limits; works without it too
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install warera-client
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requires Python 3.10+.
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
### Async (recommended)
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import asyncio
|
|
68
|
+
from warera import WareraClient
|
|
69
|
+
from warera._enums import RankingType, BattleFilter
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
# API key is optional — reads WARERA_API_KEY env var automatically
|
|
73
|
+
async with WareraClient(api_key="YOUR_KEY") as client:
|
|
74
|
+
|
|
75
|
+
# Simple lookups
|
|
76
|
+
user = await client.user.get_lite("12345")
|
|
77
|
+
country = await client.country.find_by_name("Ukraine")
|
|
78
|
+
gov = await client.government.get(country.id)
|
|
79
|
+
prices = await client.item_trading.get_prices()
|
|
80
|
+
|
|
81
|
+
print(user.username, country.name, gov.has_president())
|
|
82
|
+
print(f"Iron: {prices.get('iron').price}")
|
|
83
|
+
|
|
84
|
+
# Paginated — stream all users in a country
|
|
85
|
+
async for u in client.user.paginate_by_country(country.id, limit=50):
|
|
86
|
+
print(u.username)
|
|
87
|
+
|
|
88
|
+
# Rankings
|
|
89
|
+
top = await client.ranking.get(RankingType.USER_WEALTH)
|
|
90
|
+
for entry in top[:5]:
|
|
91
|
+
print(f"#{entry.rank} {entry.name}: {entry.value}")
|
|
92
|
+
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Sync
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from warera.sync import WareraClient
|
|
100
|
+
|
|
101
|
+
client = WareraClient(api_key="YOUR_KEY")
|
|
102
|
+
|
|
103
|
+
user = client.user.get_lite("12345")
|
|
104
|
+
prices = client.item_trading.get_prices()
|
|
105
|
+
battles = client.battle.get_active() # collects all pages automatically
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Option 1 — pass key directly
|
|
112
|
+
client = WareraClient(api_key="abc123")
|
|
113
|
+
|
|
114
|
+
# Option 2 — environment variable (recommended for scripts)
|
|
115
|
+
# export WARERA_API_KEY=abc123
|
|
116
|
+
client = WareraClient() # key picked up automatically
|
|
117
|
+
|
|
118
|
+
# Option 3 — no key (anonymous, lower rate limits, header omitted entirely)
|
|
119
|
+
client = WareraClient()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## All Resource Methods
|
|
125
|
+
|
|
126
|
+
### `client.user`
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
await client.user.get_lite(user_id: str) -> User
|
|
130
|
+
await client.user.get_by_country(country_id, *, limit=10, cursor=None) -> CursorPage[User]
|
|
131
|
+
await client.user.paginate_by_country(country_id, **kwargs) # async generator
|
|
132
|
+
await client.user.collect_by_country(country_id, **kwargs) -> list[User]
|
|
133
|
+
await client.user.get_many(user_ids: list[str], batch_size=50) -> list[User]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `client.company`
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
await client.company.get(company_id: str) -> Company
|
|
140
|
+
await client.company.get_companies(*, user_id=None, per_page=10, cursor=None) -> CursorPage[Company]
|
|
141
|
+
await client.company.get_by_user(user_id, **kwargs) -> list[Company]
|
|
142
|
+
await client.company.paginate(**kwargs) # async generator
|
|
143
|
+
await client.company.get_many(company_ids: list[str], batch_size=50) -> list[Company]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `client.country`
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
await client.country.get(country_id: str) -> Country
|
|
150
|
+
await client.country.get_all() -> dict[str, Country]
|
|
151
|
+
await client.country.find_by_name(name: str) -> Country | None
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `client.government`
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
await client.government.get(country_id: str) -> Government
|
|
158
|
+
# gov.has_president() -> bool
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `client.region`
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
await client.region.get(region_id: str) -> Region
|
|
165
|
+
await client.region.get_all() -> dict[str, Region]
|
|
166
|
+
await client.region.get_many(region_ids: list[str], batch_size=50) -> list[Region]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `client.battle`
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
await client.battle.get(battle_id: str) -> Battle
|
|
173
|
+
await client.battle.get_live(battle_id, *, round_number=None) -> BattleLive
|
|
174
|
+
await client.battle.get_many(*, is_active=None, limit=10, cursor=None,
|
|
175
|
+
direction=None, filter=None, defender_region_id=None,
|
|
176
|
+
war_id=None, country_id=None) -> CursorPage[Battle]
|
|
177
|
+
await client.battle.get_active(**kwargs) -> list[Battle]
|
|
178
|
+
await client.battle.paginate(**kwargs) # async generator
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Enums: `BattleFilter.ALL / YOUR_COUNTRY / YOUR_ENEMIES`, `BattleDirection.FORWARD / BACKWARD`
|
|
182
|
+
|
|
183
|
+
### `client.battle_ranking`
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
await client.battle_ranking.get(
|
|
187
|
+
data_type: BattleRankingDataType,
|
|
188
|
+
type: BattleRankingEntityType,
|
|
189
|
+
side: BattleRankingSide,
|
|
190
|
+
*, battle_id=None, round_id=None, war_id=None
|
|
191
|
+
) -> list[BattleRankingEntry]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Enums: `BattleRankingDataType.DAMAGE / POINTS / MONEY`,
|
|
195
|
+
`BattleRankingEntityType.USER / COUNTRY / MU`,
|
|
196
|
+
`BattleRankingSide.ATTACKER / DEFENDER`
|
|
197
|
+
|
|
198
|
+
### `client.round`
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
await client.round.get(round_id: str) -> Round
|
|
202
|
+
await client.round.get_last_hits(round_id: str) -> list[Hit]
|
|
203
|
+
await client.round.get_many(round_ids: list[str], batch_size=50) -> list[Round]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `client.event`
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
await client.event.get_paginated(*, limit=10, cursor=None,
|
|
210
|
+
country_id=None, event_types=None) -> CursorPage[Event]
|
|
211
|
+
await client.event.paginate(**kwargs) # async generator
|
|
212
|
+
await client.event.collect_all(**kwargs) -> list[Event]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Enum: `EventType` — 21 values including `WAR_DECLARED`, `BATTLE_OPENED`, `REGION_LIBERATED`, etc.
|
|
216
|
+
|
|
217
|
+
### `client.item_trading`
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
await client.item_trading.get_prices() -> dict[str, ItemPrice]
|
|
221
|
+
await client.item_trading.get_price(item_code: str) -> ItemPrice | None
|
|
222
|
+
await client.item_trading.get_top_orders(item_code, *, limit=10) -> list[TradingOrder]
|
|
223
|
+
await client.item_trading.get_offer(item_offer_id: str) -> ItemOffer
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### `client.work_offer`
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
await client.work_offer.get(work_offer_id: str) -> WorkOffer
|
|
230
|
+
await client.work_offer.get_by_company(company_id: str) -> list[WorkOffer]
|
|
231
|
+
await client.work_offer.get_paginated(*, limit=10, cursor=None, user_id=None,
|
|
232
|
+
region_id=None, energy=None, production=None, citizenship=None) -> CursorPage[WorkOffer]
|
|
233
|
+
await client.work_offer.paginate(**kwargs) # async generator
|
|
234
|
+
await client.work_offer.collect_all(**kwargs) -> list[WorkOffer]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `client.worker`
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
await client.worker.get_workers(*, company_id=None, user_id=None) -> list[Worker]
|
|
241
|
+
await client.worker.get_total_count(user_id: str) -> int
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `client.mu`
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
await client.mu.get(mu_id: str) -> MilitaryUnit
|
|
248
|
+
await client.mu.get_paginated(*, limit=20, cursor=None, member_id=None,
|
|
249
|
+
user_id=None, search=None) -> CursorPage[MilitaryUnit]
|
|
250
|
+
await client.mu.paginate(**kwargs) # async generator
|
|
251
|
+
await client.mu.collect_all(**kwargs) -> list[MilitaryUnit]
|
|
252
|
+
await client.mu.get_many(mu_ids: list[str], batch_size=50) -> list[MilitaryUnit]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `client.ranking`
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
await client.ranking.get(ranking_type: RankingType) -> list[RankingEntry]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`RankingType` enum — 26 values:
|
|
262
|
+
|
|
263
|
+
| Category | Values |
|
|
264
|
+
|----------|--------|
|
|
265
|
+
| Country | `WEEKLY_COUNTRY_DAMAGES` `WEEKLY_COUNTRY_DAMAGES_PER_CITIZEN` `COUNTRY_REGION_DIFF` `COUNTRY_DEVELOPMENT` `COUNTRY_ACTIVE_POPULATION` `COUNTRY_DAMAGES` `COUNTRY_WEALTH` `COUNTRY_PRODUCTION_BONUS` `COUNTRY_BOUNTY` |
|
|
266
|
+
| User | `WEEKLY_USER_DAMAGES` `USER_DAMAGES` `USER_WEALTH` `USER_LEVEL` `USER_REFERRALS` `USER_SUBSCRIBERS` `USER_TERRAIN` `USER_PREMIUM_MONTHS` `USER_PREMIUM_GIFTS` `USER_CASES_OPENED` `USER_GEMS_PURCHASED` `USER_BOUNTY` |
|
|
267
|
+
| MU | `MU_WEEKLY_DAMAGES` `MU_DAMAGES` `MU_TERRAIN` `MU_WEALTH` `MU_BOUNTY` |
|
|
268
|
+
|
|
269
|
+
### `client.transaction`
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
await client.transaction.get_paginated(*, limit=10, cursor=None,
|
|
273
|
+
user_id=None, mu_id=None, country_id=None, party_id=None,
|
|
274
|
+
item_code=None,
|
|
275
|
+
transaction_type: TransactionType | list[TransactionType] | None = None
|
|
276
|
+
) -> CursorPage[Transaction]
|
|
277
|
+
await client.transaction.paginate(**kwargs) # async generator
|
|
278
|
+
await client.transaction.collect_all(**kwargs) -> list[Transaction]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`TransactionType`: `APPLICATION_FEE` `TRADING` `ITEM_MARKET` `WAGE` `DONATION` `ARTICLE_TIP` `OPEN_CASE` `CRAFT_ITEM` `DISMANTLE_ITEM`
|
|
282
|
+
|
|
283
|
+
### `client.upgrade`
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
await client.upgrade.get(upgrade_type: UpgradeType, *,
|
|
287
|
+
region_id=None, company_id=None, mu_id=None) -> Upgrade
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`UpgradeType`: `BUNKER` `BASE` `PACIFICATION_CENTER` `STORAGE` `AUTOMATED_ENGINE` `BREAK_ROOM` `HEADQUARTERS` `DORMITORIES`
|
|
291
|
+
|
|
292
|
+
### `client.article`
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
await client.article.get(article_id: str) -> Article
|
|
296
|
+
await client.article.get_lite(article_id: str) -> ArticleLite
|
|
297
|
+
await client.article.get_paginated(type: ArticleType, *, limit=10, cursor=None,
|
|
298
|
+
user_id=None, categories=None, languages=None,
|
|
299
|
+
positive_score_only=None) -> CursorPage[ArticleLite]
|
|
300
|
+
await client.article.paginate(type, **kwargs) # async generator
|
|
301
|
+
await client.article.collect_all(type, **kwargs) -> list[ArticleLite]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`ArticleType`: `DAILY` `WEEKLY` `TOP` `MY` `SUBSCRIPTIONS` `LAST`
|
|
305
|
+
|
|
306
|
+
### `client.search`
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
await client.search.query(search_text: str) -> SearchResults
|
|
310
|
+
# results.results -> list[SearchResult] (id, type, name, image)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### `client.game_config`
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
await client.game_config.get_dates() -> GameDates
|
|
317
|
+
await client.game_config.get() -> GameConfig
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Pagination
|
|
323
|
+
|
|
324
|
+
Every paginated endpoint has three calling patterns:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
# 1. Single page — manual cursor control
|
|
328
|
+
page = await client.battle.get_many(is_active=True, limit=20)
|
|
329
|
+
print(page.items) # list[Battle]
|
|
330
|
+
print(page.next_cursor) # str | None
|
|
331
|
+
print(page.has_more) # bool
|
|
332
|
+
|
|
333
|
+
# 2. Async generator — yields items one by one across all pages
|
|
334
|
+
async for battle in client.battle.paginate(is_active=True):
|
|
335
|
+
print(battle.id)
|
|
336
|
+
|
|
337
|
+
# 3. Collect all into a flat list
|
|
338
|
+
all_battles = await client.battle.get_active()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Batch Requests
|
|
344
|
+
|
|
345
|
+
Send multiple procedures in **one HTTP round-trip** using `BatchSession`.
|
|
346
|
+
|
|
347
|
+
### Mixed procedures
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
async with client.batch() as batch:
|
|
351
|
+
country_item = batch.add("country.getCountryById", {"countryId": "7"})
|
|
352
|
+
gov_item = batch.add("government.getByCountryId", {"countryId": "7"})
|
|
353
|
+
prices_item = batch.add("itemTrading.getPrices", {})
|
|
354
|
+
dates_item = batch.add("gameConfig.getDates", {})
|
|
355
|
+
|
|
356
|
+
# After the block — all resolved in one POST:
|
|
357
|
+
country = country_item.result # raw dict (no model parsing in manual batch)
|
|
358
|
+
gov = gov_item.result
|
|
359
|
+
prices = prices_item.result
|
|
360
|
+
dates = dates_item.result
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Batch-fetch many IDs (auto-chunked)
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
# Fetches 200 companies in 4 concurrent batches of 50
|
|
367
|
+
companies = await client.company.get_many(company_ids) # list[Company]
|
|
368
|
+
users = await client.user.get_many(user_ids) # list[User]
|
|
369
|
+
regions = await client.region.get_many(region_ids) # list[Region]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Partial failure handling
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
async with client.batch() as batch:
|
|
376
|
+
good = batch.add("country.getAllCountries", {})
|
|
377
|
+
bad = batch.add("company.getById", {"companyId": "nonexistent"})
|
|
378
|
+
|
|
379
|
+
print(good.ok) # True
|
|
380
|
+
print(bad.ok) # False
|
|
381
|
+
if not bad.ok:
|
|
382
|
+
print(bad._error) # WareraNotFoundError
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Wire format (for reference)
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
POST /trpc/proc0,proc1,proc2?batch=1
|
|
389
|
+
Content-Type: application/json
|
|
390
|
+
X-API-Key: <token>
|
|
391
|
+
|
|
392
|
+
{"0": {input0}, "1": {input1}, "2": {input2}}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Error Handling
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
from warera.exceptions import (
|
|
401
|
+
WareraError, # base — catch everything
|
|
402
|
+
WareraUnauthorizedError, # 401 — bad/missing API key
|
|
403
|
+
WareraForbiddenError, # 403
|
|
404
|
+
WareraNotFoundError, # 404
|
|
405
|
+
WareraRateLimitError, # 429 — auto-retried; raised after all retries exhausted
|
|
406
|
+
WareraServerError, # 5xx — auto-retried
|
|
407
|
+
WareraValidationError, # Pydantic parse failure
|
|
408
|
+
WareraBatchError, # one or more batch items failed
|
|
409
|
+
# .errors → dict[int, WareraError]
|
|
410
|
+
# .results → dict[int, Any]
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
user = await client.user.get_lite("99999")
|
|
415
|
+
except WareraNotFoundError:
|
|
416
|
+
print("User not found")
|
|
417
|
+
except WareraRateLimitError:
|
|
418
|
+
print("Still hitting rate limits after 3 retries")
|
|
419
|
+
except WareraError as e:
|
|
420
|
+
print(f"API error: {e}")
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Configuration
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
WareraClient(
|
|
429
|
+
api_key: str | None = None, # also reads WARERA_API_KEY env var
|
|
430
|
+
base_url: str = "https://api2.warera.io/trpc",
|
|
431
|
+
timeout: float = 10.0, # seconds
|
|
432
|
+
max_retries: int = 3,
|
|
433
|
+
retry_backoff_factor: float = 0.5,
|
|
434
|
+
batch_size: int = 50, # default max procedures per batch POST
|
|
435
|
+
)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Project Structure
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
warera/
|
|
444
|
+
├── __init__.py # public API surface
|
|
445
|
+
├── client.py # WareraClient
|
|
446
|
+
├── sync.py # sync shim
|
|
447
|
+
├── exceptions.py # error hierarchy
|
|
448
|
+
├── _enums.py # all StrEnum classes from schema
|
|
449
|
+
├── _http.py # httpx session, GET/POST encoding, retry
|
|
450
|
+
├── _pagination.py # paginate(), collect_all()
|
|
451
|
+
├── _batch.py # BatchSession, BatchItem, fetch_many_by_ids
|
|
452
|
+
├── models/ # Pydantic response models (20 files)
|
|
453
|
+
└── resources/ # Resource classes (19 files)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Development
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
git clone https://github.com/you/warera-client
|
|
462
|
+
cd warera-client
|
|
463
|
+
pip install -e ".[dev]"
|
|
464
|
+
|
|
465
|
+
# Unit tests (no API key needed)
|
|
466
|
+
pytest tests/unit/ -v
|
|
467
|
+
|
|
468
|
+
# Integration tests (live API)
|
|
469
|
+
WARERA_API_KEY=your_key pytest tests/integration/ -v
|
|
470
|
+
|
|
471
|
+
# Lint + type check
|
|
472
|
+
ruff check warera/
|
|
473
|
+
mypy warera/
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## License
|
|
479
|
+
|
|
480
|
+
MIT
|