cosplay2py 0.3.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.
- cosplay2py-0.3.0/LICENSE +21 -0
- cosplay2py-0.3.0/PKG-INFO +199 -0
- cosplay2py-0.3.0/README.md +169 -0
- cosplay2py-0.3.0/pyproject.toml +113 -0
- cosplay2py-0.3.0/pyproject.toml.orig +110 -0
- cosplay2py-0.3.0/src/cosplay2py/__init__.py +38 -0
- cosplay2py-0.3.0/src/cosplay2py/_base_client.py +220 -0
- cosplay2py-0.3.0/src/cosplay2py/_client.py +240 -0
- cosplay2py-0.3.0/src/cosplay2py/_exceptions.py +125 -0
- cosplay2py-0.3.0/src/cosplay2py/_types.py +48 -0
- cosplay2py-0.3.0/src/cosplay2py/models/__init__.py +184 -0
- cosplay2py-0.3.0/src/cosplay2py/models/_base.py +24 -0
- cosplay2py-0.3.0/src/cosplay2py/models/common.py +49 -0
- cosplay2py-0.3.0/src/cosplay2py/models/events.py +296 -0
- cosplay2py-0.3.0/src/cosplay2py/models/misc.py +118 -0
- cosplay2py-0.3.0/src/cosplay2py/models/requests.py +290 -0
- cosplay2py-0.3.0/src/cosplay2py/models/topics.py +215 -0
- cosplay2py-0.3.0/src/cosplay2py/models/users.py +114 -0
- cosplay2py-0.3.0/src/cosplay2py/py.typed +0 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/__init__.py +44 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/_base.py +21 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/events.py +552 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/geo.py +33 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/misc.py +1017 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/requests.py +826 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/topics.py +579 -0
- cosplay2py-0.3.0/src/cosplay2py/resources/users.py +211 -0
cosplay2py-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arutemu64
|
|
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,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cosplay2py
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Unofficial typed sync and async client for the Cosplay2.ru API
|
|
5
|
+
Keywords: cosplay2,cosplay,events,api,client,async,typed,pydantic,httpx2
|
|
6
|
+
Author: Arutemu64
|
|
7
|
+
Author-email: Arutemu64 <arutemu64@outlook.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
|
+
Classifier: Framework :: AnyIO
|
|
20
|
+
Classifier: Framework :: Pydantic :: 2
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Dist: anyio>=4.0.0
|
|
23
|
+
Requires-Dist: httpx2>=2.0.0
|
|
24
|
+
Requires-Dist: pydantic>=2.4.0
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Project-URL: Homepage, https://github.com/Arutemu64/cosplay2py
|
|
27
|
+
Project-URL: Repository, https://github.com/Arutemu64/cosplay2py
|
|
28
|
+
Project-URL: Issues, https://github.com/Arutemu64/cosplay2py/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# cosplay2py
|
|
32
|
+
|
|
33
|
+
Unofficial typed sync and async client for the [Cosplay2.ru](https://cosplay2.ru) API, built on
|
|
34
|
+
[httpx2](https://github.com/pydantic/httpx2) and [Pydantic](https://docs.pydantic.dev).
|
|
35
|
+
|
|
36
|
+
> [!WARNING]
|
|
37
|
+
> This project is AI-generated: most of the code, tests and documentation were written
|
|
38
|
+
> with an AI assistant. Review it before relying on it, and expect rough edges.
|
|
39
|
+
|
|
40
|
+
## Installing
|
|
41
|
+
|
|
42
|
+
The package is not on PyPI. Install it from a release tag:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
uv add "cosplay2py @ git+https://github.com/Arutemu64/cosplay2py" --tag v0.2.0
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Versions follow SemVer. On `0.x`, a minor bump (`0.1` → `0.2`) may break things and a
|
|
49
|
+
patch bump never does. See [CHANGELOG.md](CHANGELOG.md).
|
|
50
|
+
|
|
51
|
+
## Authentication
|
|
52
|
+
|
|
53
|
+
API tokens are issued **per event**: on the event site open *Орги → Настройка → API токены*
|
|
54
|
+
and create a token. You get an API key and an API secret (the secret is shown once;
|
|
55
|
+
tokens expire after 30 days). The client sends them as `X-API-Key` / `X-API-Secret`.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from cosplay2py import Cosplay2
|
|
61
|
+
from cosplay2py.models import BlankId, RequestStatus
|
|
62
|
+
|
|
63
|
+
with Cosplay2(subdomain="ff26summer", api_key="...", api_secret="...") as c2:
|
|
64
|
+
event = c2.events.get_settings()
|
|
65
|
+
print(event.title, event.timezone)
|
|
66
|
+
|
|
67
|
+
for topic in c2.topics.get_list():
|
|
68
|
+
listing = c2.topics.get_requests(topic.url_code)
|
|
69
|
+
approved = [r for r in listing.requests if r.status is RequestStatus.APPROVED]
|
|
70
|
+
print(topic.title, len(approved))
|
|
71
|
+
|
|
72
|
+
details = c2.requests.get(687475)
|
|
73
|
+
print(details.values_by_field())
|
|
74
|
+
|
|
75
|
+
jury_sheet = c2.events.get_blank(BlankId.JURY).html
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Async (asyncio or trio):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from cosplay2py import AsyncCosplay2
|
|
82
|
+
|
|
83
|
+
async with AsyncCosplay2(subdomain="ff26summer") as c2:
|
|
84
|
+
state = await c2.events.get_state()
|
|
85
|
+
print(state.requests_total)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Events on a custom domain: `Cosplay2(base_url="https://fancom.info", ...)`.
|
|
89
|
+
|
|
90
|
+
Any argument left out falls back to the environment: `COSPLAY2_API_KEY`,
|
|
91
|
+
`COSPLAY2_API_SECRET`, `COSPLAY2_BASE_URL`.
|
|
92
|
+
|
|
93
|
+
### Resources
|
|
94
|
+
|
|
95
|
+
| Attribute | Methods |
|
|
96
|
+
|---------------|---------|
|
|
97
|
+
| `events` | `get_settings`, `save_setting`, `get_state`, `get_topics_state`, `get_topics_statistic`, `get_staff`, `add_org`, `delete_org`, `change_main_org`, `add_curator`, `delete_curator`, `get_winners`, `get_plan`, `save_plan`, `get_blank`, `get_reg_topics`, `get_reg_alphabet`, `search`, `set_deadline` |
|
|
98
|
+
| `topics` | `get_list`, `get_requests`, `get_all_requests`, `get_requests_for_planner`, `get_form`, `get_with_groups`, `add_child_topic`, `delete_child_topic`, `create`, `delete`, `reorder`, `fill_from_template`, `fill_from_topic`, `add_section`, `save_section`, `delete_section`, `add_field`, `delete_field`, `save_field`, `save_topic`, `reorder_section`, `reorder_field`, `generate_card_titles`, `reorder_requests` |
|
|
99
|
+
| `requests` | `get`, `get_comments`, `get_org_comments`, `get_user`, `generate_card_title`, `generate_card_announcement`, `generate_card_diplom`, `generate_voting_number`, `search`, `get_value_history`, `add_comment`, `delete_comment`, `save_field`, `upload_image`, `delete_image`, `upload_file`, `delete_file`, `save_data`, `set_status`, `create`, `create_section`, `delete_section`, `change_topic`, `add_org_comment`, `delete_org_comment`, `get_feedbacks`, `add_feedback`, `delete_feedback` |
|
|
100
|
+
| `users` | `search` |
|
|
101
|
+
| `design` | `get`, `get_images`, `save`, `upload_image`, `delete_image`, `delete_panel_logo`, `delete_header_logo`, `delete_site_background` |
|
|
102
|
+
| `pages` | `get_list`, `get`, `create`, `save_field`, `save_fields`, `delete`, `reorder` |
|
|
103
|
+
| `media` | `get_data`, `add_album`, `save_album_field`, `delete_album` |
|
|
104
|
+
| `cards` | `get_topics_and_cards`, `get_card`, `get_topics_and_requests`, `get_request` |
|
|
105
|
+
| `etickets` | `get_list`, `get_types`, `add_type`, `delete_type`, `reorder_type`, `save_type_field`, `save_field`, `delete`, `create`, `get`, `get_status`, `save_personal_data` |
|
|
106
|
+
| `tickets` | `get_statistic`, `get_voting_statistics`, `create`, `clear_votes`, `add_prize`, `delete_prize`, `save_prize_field`, `save_prize_handed`, `get_ticket`, `check_for_voting`, `get_voting_data`, `get_cards`, `select_winner` |
|
|
107
|
+
|
|
108
|
+
### Data conventions
|
|
109
|
+
|
|
110
|
+
- Server timestamps become timezone-aware UTC `datetime`s, whether the API sent
|
|
111
|
+
`"YYYY-MM-DD HH:MM:SS"`, Unix seconds or Unix milliseconds.
|
|
112
|
+
- Display strings like `"22.08.26 03:00"` (`TopicState`, `Event.start_time`) become
|
|
113
|
+
naive `datetime`s, as the site shows them.
|
|
114
|
+
- `0`/`1` flags become `bool`. JSON packed into strings (`Event.design`, the plan) is parsed.
|
|
115
|
+
- Closed domain sets (`RequestStatus`, `TopicFieldType`, `PageType`, `PlanNodeType`,
|
|
116
|
+
`EventRole`, `EventStatus`, `EventTime`, `TopicCategory`,
|
|
117
|
+
`PublicRequests`, `TopicTemplate`, `SectionTemplate`, `FieldTemplate`, `FeedbackMark`,
|
|
118
|
+
`CardField`, `TicketKind`, `TicketCreateMode`, `VotingType`, `BlankId`, `Deadline`) are
|
|
119
|
+
validated strictly as `StrEnum` / `IntEnum` members (empty strings on optional fields
|
|
120
|
+
normalize to `None`).
|
|
121
|
+
- Unknown fields are kept rather than rejected. Read them via `model.model_extra`.
|
|
122
|
+
|
|
123
|
+
Talking to the API without this library? [docs/api-notes.md](docs/api-notes.md)
|
|
124
|
+
describes its endpoints, wire formats and quirks.
|
|
125
|
+
|
|
126
|
+
### Errors
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
Cosplay2Error
|
|
130
|
+
└── APIError
|
|
131
|
+
├── APIConnectionError
|
|
132
|
+
│ └── APITimeoutError
|
|
133
|
+
├── APIResponseValidationError # 2xx, but not the expected JSON
|
|
134
|
+
└── APIStatusError # non-2xx; .status_code, .body, .response
|
|
135
|
+
├── BadRequestError (400)
|
|
136
|
+
├── AuthenticationError (401)
|
|
137
|
+
├── PermissionDeniedError (403)
|
|
138
|
+
├── NotFoundError (404)
|
|
139
|
+
├── RateLimitError (429)
|
|
140
|
+
└── InternalServerError (5xx)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Repeatable reads retry transport failures and 429/502/503/504 responses, with
|
|
144
|
+
exponential backoff and jitter (2 retries by default, configurable with
|
|
145
|
+
`max_retries`). Writes retry only connection failures (`ConnectError`), never
|
|
146
|
+
HTTP responses or read/write timeouts. An ambiguous failure may mean a write
|
|
147
|
+
succeeded; check the service before repeating it yourself.
|
|
148
|
+
|
|
149
|
+
`Retry-After` supports seconds and HTTP dates. If the server asks for more than
|
|
150
|
+
30 seconds, the error is returned immediately instead of retrying early.
|
|
151
|
+
Error messages omit response bodies and validation inputs. Inspect `.response`
|
|
152
|
+
or `.body` explicitly when debugging; these can contain personal data.
|
|
153
|
+
|
|
154
|
+
### Customizing HTTP
|
|
155
|
+
|
|
156
|
+
Pass `timeout=` (seconds or `httpx2.Timeout`), extra `headers=`, or a fully configured
|
|
157
|
+
`http_client=httpx2.Client(...)` / `httpx2.AsyncClient(...)` for proxies, custom
|
|
158
|
+
transports or testing with `httpx2.MockTransport`. A client you pass in is not closed by
|
|
159
|
+
the wrapper. Its timeout and default headers apply; wrapper headers override
|
|
160
|
+
matching defaults, and redirects are always disabled. `timeout=` applies only to a
|
|
161
|
+
client created by the wrapper. Timeouts limit network phases, not the total time
|
|
162
|
+
across retries. Use HTTPS for event credentials; HTTP is supported for local testing.
|
|
163
|
+
Base URLs cannot contain userinfo, query strings or fragments.
|
|
164
|
+
|
|
165
|
+
This client follows observed site behavior, which can change without notice.
|
|
166
|
+
Enable `logging.getLogger("cosplay2py")` at `DEBUG` to see requests.
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to propose changes.
|
|
171
|
+
|
|
172
|
+
```shell
|
|
173
|
+
uv sync
|
|
174
|
+
uv run prek install # install Git hooks once per clone
|
|
175
|
+
uv run pytest # offline unit tests (default)
|
|
176
|
+
uv run prek run --all-files # lint, format, and type-check
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Testing against the real site
|
|
180
|
+
|
|
181
|
+
`tests/live` holds read-only smoke tests that are deselected by default. They need a token
|
|
182
|
+
for the event (PowerShell shown):
|
|
183
|
+
|
|
184
|
+
```powershell
|
|
185
|
+
$env:COSPLAY2_API_KEY = "..."
|
|
186
|
+
$env:COSPLAY2_API_SECRET = "..."
|
|
187
|
+
$env:COSPLAY2_BASE_URL = "https://ff26summer.cosplay2.ru"
|
|
188
|
+
uv run pytest -m live -rw
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`-rw` lists warnings: fields the API returns that the models don't declare yet show up
|
|
192
|
+
as `UnknownFieldsWarning`, an early sign of API drift.
|
|
193
|
+
|
|
194
|
+
Portal reads work with the configured event base URL (verified with event API
|
|
195
|
+
credentials); `https://cosplay2.ru` returned HTTP 403 with those credentials. Methods: `events.filter_list()`,
|
|
196
|
+
`events.get_types()`, `events.create_festival()`, `events.create_festival_logo()`,
|
|
197
|
+
`users.ratings_global()` and the country/area/region/city variants,
|
|
198
|
+
`users.get_region_geo_id()`, and `geo.code_city()`. Unobserved response shapes
|
|
199
|
+
are returned as JSON data.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# cosplay2py
|
|
2
|
+
|
|
3
|
+
Unofficial typed sync and async client for the [Cosplay2.ru](https://cosplay2.ru) API, built on
|
|
4
|
+
[httpx2](https://github.com/pydantic/httpx2) and [Pydantic](https://docs.pydantic.dev).
|
|
5
|
+
|
|
6
|
+
> [!WARNING]
|
|
7
|
+
> This project is AI-generated: most of the code, tests and documentation were written
|
|
8
|
+
> with an AI assistant. Review it before relying on it, and expect rough edges.
|
|
9
|
+
|
|
10
|
+
## Installing
|
|
11
|
+
|
|
12
|
+
The package is not on PyPI. Install it from a release tag:
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
uv add "cosplay2py @ git+https://github.com/Arutemu64/cosplay2py" --tag v0.2.0
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Versions follow SemVer. On `0.x`, a minor bump (`0.1` → `0.2`) may break things and a
|
|
19
|
+
patch bump never does. See [CHANGELOG.md](CHANGELOG.md).
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
|
|
23
|
+
API tokens are issued **per event**: on the event site open *Орги → Настройка → API токены*
|
|
24
|
+
and create a token. You get an API key and an API secret (the secret is shown once;
|
|
25
|
+
tokens expire after 30 days). The client sends them as `X-API-Key` / `X-API-Secret`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from cosplay2py import Cosplay2
|
|
31
|
+
from cosplay2py.models import BlankId, RequestStatus
|
|
32
|
+
|
|
33
|
+
with Cosplay2(subdomain="ff26summer", api_key="...", api_secret="...") as c2:
|
|
34
|
+
event = c2.events.get_settings()
|
|
35
|
+
print(event.title, event.timezone)
|
|
36
|
+
|
|
37
|
+
for topic in c2.topics.get_list():
|
|
38
|
+
listing = c2.topics.get_requests(topic.url_code)
|
|
39
|
+
approved = [r for r in listing.requests if r.status is RequestStatus.APPROVED]
|
|
40
|
+
print(topic.title, len(approved))
|
|
41
|
+
|
|
42
|
+
details = c2.requests.get(687475)
|
|
43
|
+
print(details.values_by_field())
|
|
44
|
+
|
|
45
|
+
jury_sheet = c2.events.get_blank(BlankId.JURY).html
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Async (asyncio or trio):
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from cosplay2py import AsyncCosplay2
|
|
52
|
+
|
|
53
|
+
async with AsyncCosplay2(subdomain="ff26summer") as c2:
|
|
54
|
+
state = await c2.events.get_state()
|
|
55
|
+
print(state.requests_total)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Events on a custom domain: `Cosplay2(base_url="https://fancom.info", ...)`.
|
|
59
|
+
|
|
60
|
+
Any argument left out falls back to the environment: `COSPLAY2_API_KEY`,
|
|
61
|
+
`COSPLAY2_API_SECRET`, `COSPLAY2_BASE_URL`.
|
|
62
|
+
|
|
63
|
+
### Resources
|
|
64
|
+
|
|
65
|
+
| Attribute | Methods |
|
|
66
|
+
|---------------|---------|
|
|
67
|
+
| `events` | `get_settings`, `save_setting`, `get_state`, `get_topics_state`, `get_topics_statistic`, `get_staff`, `add_org`, `delete_org`, `change_main_org`, `add_curator`, `delete_curator`, `get_winners`, `get_plan`, `save_plan`, `get_blank`, `get_reg_topics`, `get_reg_alphabet`, `search`, `set_deadline` |
|
|
68
|
+
| `topics` | `get_list`, `get_requests`, `get_all_requests`, `get_requests_for_planner`, `get_form`, `get_with_groups`, `add_child_topic`, `delete_child_topic`, `create`, `delete`, `reorder`, `fill_from_template`, `fill_from_topic`, `add_section`, `save_section`, `delete_section`, `add_field`, `delete_field`, `save_field`, `save_topic`, `reorder_section`, `reorder_field`, `generate_card_titles`, `reorder_requests` |
|
|
69
|
+
| `requests` | `get`, `get_comments`, `get_org_comments`, `get_user`, `generate_card_title`, `generate_card_announcement`, `generate_card_diplom`, `generate_voting_number`, `search`, `get_value_history`, `add_comment`, `delete_comment`, `save_field`, `upload_image`, `delete_image`, `upload_file`, `delete_file`, `save_data`, `set_status`, `create`, `create_section`, `delete_section`, `change_topic`, `add_org_comment`, `delete_org_comment`, `get_feedbacks`, `add_feedback`, `delete_feedback` |
|
|
70
|
+
| `users` | `search` |
|
|
71
|
+
| `design` | `get`, `get_images`, `save`, `upload_image`, `delete_image`, `delete_panel_logo`, `delete_header_logo`, `delete_site_background` |
|
|
72
|
+
| `pages` | `get_list`, `get`, `create`, `save_field`, `save_fields`, `delete`, `reorder` |
|
|
73
|
+
| `media` | `get_data`, `add_album`, `save_album_field`, `delete_album` |
|
|
74
|
+
| `cards` | `get_topics_and_cards`, `get_card`, `get_topics_and_requests`, `get_request` |
|
|
75
|
+
| `etickets` | `get_list`, `get_types`, `add_type`, `delete_type`, `reorder_type`, `save_type_field`, `save_field`, `delete`, `create`, `get`, `get_status`, `save_personal_data` |
|
|
76
|
+
| `tickets` | `get_statistic`, `get_voting_statistics`, `create`, `clear_votes`, `add_prize`, `delete_prize`, `save_prize_field`, `save_prize_handed`, `get_ticket`, `check_for_voting`, `get_voting_data`, `get_cards`, `select_winner` |
|
|
77
|
+
|
|
78
|
+
### Data conventions
|
|
79
|
+
|
|
80
|
+
- Server timestamps become timezone-aware UTC `datetime`s, whether the API sent
|
|
81
|
+
`"YYYY-MM-DD HH:MM:SS"`, Unix seconds or Unix milliseconds.
|
|
82
|
+
- Display strings like `"22.08.26 03:00"` (`TopicState`, `Event.start_time`) become
|
|
83
|
+
naive `datetime`s, as the site shows them.
|
|
84
|
+
- `0`/`1` flags become `bool`. JSON packed into strings (`Event.design`, the plan) is parsed.
|
|
85
|
+
- Closed domain sets (`RequestStatus`, `TopicFieldType`, `PageType`, `PlanNodeType`,
|
|
86
|
+
`EventRole`, `EventStatus`, `EventTime`, `TopicCategory`,
|
|
87
|
+
`PublicRequests`, `TopicTemplate`, `SectionTemplate`, `FieldTemplate`, `FeedbackMark`,
|
|
88
|
+
`CardField`, `TicketKind`, `TicketCreateMode`, `VotingType`, `BlankId`, `Deadline`) are
|
|
89
|
+
validated strictly as `StrEnum` / `IntEnum` members (empty strings on optional fields
|
|
90
|
+
normalize to `None`).
|
|
91
|
+
- Unknown fields are kept rather than rejected. Read them via `model.model_extra`.
|
|
92
|
+
|
|
93
|
+
Talking to the API without this library? [docs/api-notes.md](docs/api-notes.md)
|
|
94
|
+
describes its endpoints, wire formats and quirks.
|
|
95
|
+
|
|
96
|
+
### Errors
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Cosplay2Error
|
|
100
|
+
└── APIError
|
|
101
|
+
├── APIConnectionError
|
|
102
|
+
│ └── APITimeoutError
|
|
103
|
+
├── APIResponseValidationError # 2xx, but not the expected JSON
|
|
104
|
+
└── APIStatusError # non-2xx; .status_code, .body, .response
|
|
105
|
+
├── BadRequestError (400)
|
|
106
|
+
├── AuthenticationError (401)
|
|
107
|
+
├── PermissionDeniedError (403)
|
|
108
|
+
├── NotFoundError (404)
|
|
109
|
+
├── RateLimitError (429)
|
|
110
|
+
└── InternalServerError (5xx)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Repeatable reads retry transport failures and 429/502/503/504 responses, with
|
|
114
|
+
exponential backoff and jitter (2 retries by default, configurable with
|
|
115
|
+
`max_retries`). Writes retry only connection failures (`ConnectError`), never
|
|
116
|
+
HTTP responses or read/write timeouts. An ambiguous failure may mean a write
|
|
117
|
+
succeeded; check the service before repeating it yourself.
|
|
118
|
+
|
|
119
|
+
`Retry-After` supports seconds and HTTP dates. If the server asks for more than
|
|
120
|
+
30 seconds, the error is returned immediately instead of retrying early.
|
|
121
|
+
Error messages omit response bodies and validation inputs. Inspect `.response`
|
|
122
|
+
or `.body` explicitly when debugging; these can contain personal data.
|
|
123
|
+
|
|
124
|
+
### Customizing HTTP
|
|
125
|
+
|
|
126
|
+
Pass `timeout=` (seconds or `httpx2.Timeout`), extra `headers=`, or a fully configured
|
|
127
|
+
`http_client=httpx2.Client(...)` / `httpx2.AsyncClient(...)` for proxies, custom
|
|
128
|
+
transports or testing with `httpx2.MockTransport`. A client you pass in is not closed by
|
|
129
|
+
the wrapper. Its timeout and default headers apply; wrapper headers override
|
|
130
|
+
matching defaults, and redirects are always disabled. `timeout=` applies only to a
|
|
131
|
+
client created by the wrapper. Timeouts limit network phases, not the total time
|
|
132
|
+
across retries. Use HTTPS for event credentials; HTTP is supported for local testing.
|
|
133
|
+
Base URLs cannot contain userinfo, query strings or fragments.
|
|
134
|
+
|
|
135
|
+
This client follows observed site behavior, which can change without notice.
|
|
136
|
+
Enable `logging.getLogger("cosplay2py")` at `DEBUG` to see requests.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to propose changes.
|
|
141
|
+
|
|
142
|
+
```shell
|
|
143
|
+
uv sync
|
|
144
|
+
uv run prek install # install Git hooks once per clone
|
|
145
|
+
uv run pytest # offline unit tests (default)
|
|
146
|
+
uv run prek run --all-files # lint, format, and type-check
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Testing against the real site
|
|
150
|
+
|
|
151
|
+
`tests/live` holds read-only smoke tests that are deselected by default. They need a token
|
|
152
|
+
for the event (PowerShell shown):
|
|
153
|
+
|
|
154
|
+
```powershell
|
|
155
|
+
$env:COSPLAY2_API_KEY = "..."
|
|
156
|
+
$env:COSPLAY2_API_SECRET = "..."
|
|
157
|
+
$env:COSPLAY2_BASE_URL = "https://ff26summer.cosplay2.ru"
|
|
158
|
+
uv run pytest -m live -rw
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`-rw` lists warnings: fields the API returns that the models don't declare yet show up
|
|
162
|
+
as `UnknownFieldsWarning`, an early sign of API drift.
|
|
163
|
+
|
|
164
|
+
Portal reads work with the configured event base URL (verified with event API
|
|
165
|
+
credentials); `https://cosplay2.ru` returned HTTP 403 with those credentials. Methods: `events.filter_list()`,
|
|
166
|
+
`events.get_types()`, `events.create_festival()`, `events.create_festival_logo()`,
|
|
167
|
+
`users.ratings_global()` and the country/area/region/city variants,
|
|
168
|
+
`users.get_region_geo_id()`, and `geo.code_city()`. Unobserved response shapes
|
|
169
|
+
are returned as JSON data.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cosplay2py"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Unofficial typed sync and async client for the Cosplay2.ru API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
keywords = [
|
|
9
|
+
"cosplay2",
|
|
10
|
+
"cosplay",
|
|
11
|
+
"events",
|
|
12
|
+
"api",
|
|
13
|
+
"client",
|
|
14
|
+
"async",
|
|
15
|
+
"typed",
|
|
16
|
+
"pydantic",
|
|
17
|
+
"httpx2",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: 3.15",
|
|
29
|
+
"Framework :: AnyIO",
|
|
30
|
+
"Framework :: Pydantic :: 2",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
requires-python = ">=3.12"
|
|
34
|
+
dependencies = [
|
|
35
|
+
"anyio>=4.0.0",
|
|
36
|
+
"httpx2>=2.0.0",
|
|
37
|
+
"pydantic>=2.4.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[project.authors]]
|
|
41
|
+
name = "Arutemu64"
|
|
42
|
+
email = "arutemu64@outlook.com"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/Arutemu64/cosplay2py"
|
|
46
|
+
Repository = "https://github.com/Arutemu64/cosplay2py"
|
|
47
|
+
Issues = "https://github.com/Arutemu64/cosplay2py/issues"
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
51
|
+
build-backend = "uv_build"
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"pytest>=9.2.0",
|
|
56
|
+
"ruff>=0.17.3",
|
|
57
|
+
"ty==0.0.93",
|
|
58
|
+
"trio>=0.26",
|
|
59
|
+
"prek>=0.5.7",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.pytest]
|
|
63
|
+
strict = true
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
addopts = [
|
|
66
|
+
"-m",
|
|
67
|
+
"not live",
|
|
68
|
+
]
|
|
69
|
+
markers = ["live: calls the real Cosplay2 API (needs COSPLAY2_API_KEY, COSPLAY2_API_SECRET, COSPLAY2_BASE_URL)"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
line-length = 88
|
|
73
|
+
src = [
|
|
74
|
+
"src",
|
|
75
|
+
"tests",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
extend-select = [
|
|
80
|
+
"I",
|
|
81
|
+
"UP",
|
|
82
|
+
"B",
|
|
83
|
+
"SIM",
|
|
84
|
+
"C4",
|
|
85
|
+
"PIE",
|
|
86
|
+
"RET",
|
|
87
|
+
"PT",
|
|
88
|
+
"PTH",
|
|
89
|
+
"ASYNC",
|
|
90
|
+
"DTZ",
|
|
91
|
+
"N",
|
|
92
|
+
"RUF",
|
|
93
|
+
"TID",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
97
|
+
ban-relative-imports = "all"
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.isort]
|
|
100
|
+
known-first-party = ["cosplay2py"]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.format]
|
|
103
|
+
docstring-code-format = true
|
|
104
|
+
line-ending = "lf"
|
|
105
|
+
|
|
106
|
+
[tool.ty.src]
|
|
107
|
+
include = [
|
|
108
|
+
"src",
|
|
109
|
+
"tests",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
[tool.ty.terminal]
|
|
113
|
+
error-on-warning = true
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cosplay2py"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Unofficial typed sync and async client for the Cosplay2.ru API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Arutemu64", email = "arutemu64@outlook.com" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
keywords = [
|
|
12
|
+
"cosplay2",
|
|
13
|
+
"cosplay",
|
|
14
|
+
"events",
|
|
15
|
+
"api",
|
|
16
|
+
"client",
|
|
17
|
+
"async",
|
|
18
|
+
"typed",
|
|
19
|
+
"pydantic",
|
|
20
|
+
"httpx2",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
"Programming Language :: Python :: 3.15",
|
|
32
|
+
"Framework :: AnyIO",
|
|
33
|
+
"Framework :: Pydantic :: 2",
|
|
34
|
+
"Typing :: Typed",
|
|
35
|
+
]
|
|
36
|
+
requires-python = ">=3.12"
|
|
37
|
+
# Lower bounds only: they are the oldest versions the test suite passes with
|
|
38
|
+
# (`uv run --python 3.12 --resolution lowest-direct pytest`).
|
|
39
|
+
dependencies = [
|
|
40
|
+
"anyio>=4.0.0",
|
|
41
|
+
"httpx2>=2.0.0",
|
|
42
|
+
"pydantic>=2.4.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/Arutemu64/cosplay2py"
|
|
47
|
+
Repository = "https://github.com/Arutemu64/cosplay2py"
|
|
48
|
+
Issues = "https://github.com/Arutemu64/cosplay2py/issues"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[dependency-groups]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=9.2.0",
|
|
57
|
+
"ruff>=0.17.3",
|
|
58
|
+
# Pre-1.0: releases can change diagnostics, so pin exactly.
|
|
59
|
+
"ty==0.0.93",
|
|
60
|
+
"trio>=0.26",
|
|
61
|
+
"prek>=0.5.7",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.pytest]
|
|
65
|
+
strict = true
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
# Live tests hit the real site; run them explicitly with `pytest -m live`.
|
|
68
|
+
addopts = ["-m", "not live"]
|
|
69
|
+
markers = [
|
|
70
|
+
"live: calls the real Cosplay2 API (needs COSPLAY2_API_KEY, COSPLAY2_API_SECRET, COSPLAY2_BASE_URL)",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
line-length = 88
|
|
75
|
+
src = ["src", "tests"]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
# Keep Ruff's curated default rule set (`select` would replace it) and add more.
|
|
79
|
+
extend-select = [
|
|
80
|
+
"I", # isort
|
|
81
|
+
"UP", # pyupgrade
|
|
82
|
+
"B", # flake8-bugbear
|
|
83
|
+
"SIM", # flake8-simplify
|
|
84
|
+
"C4", # flake8-comprehensions
|
|
85
|
+
"PIE", # flake8-pie
|
|
86
|
+
"RET", # flake8-return
|
|
87
|
+
"PT", # flake8-pytest-style
|
|
88
|
+
"PTH", # flake8-use-pathlib
|
|
89
|
+
"ASYNC", # flake8-async
|
|
90
|
+
"DTZ", # flake8-datetimez
|
|
91
|
+
"N", # pep8-naming
|
|
92
|
+
"RUF", # ruff-specific rules
|
|
93
|
+
"TID", # flake8-tidy-imports
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
97
|
+
ban-relative-imports = "all"
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.isort]
|
|
100
|
+
known-first-party = ["cosplay2py"]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.format]
|
|
103
|
+
docstring-code-format = true
|
|
104
|
+
line-ending = "lf"
|
|
105
|
+
|
|
106
|
+
[tool.ty.src]
|
|
107
|
+
include = ["src", "tests"]
|
|
108
|
+
|
|
109
|
+
[tool.ty.terminal]
|
|
110
|
+
error-on-warning = true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Sync and async client for the Cosplay2.ru event API."""
|
|
2
|
+
|
|
3
|
+
from cosplay2py import models
|
|
4
|
+
from cosplay2py._base_client import __version__
|
|
5
|
+
from cosplay2py._client import AsyncCosplay2, Cosplay2
|
|
6
|
+
from cosplay2py._exceptions import (
|
|
7
|
+
APIConnectionError,
|
|
8
|
+
APIError,
|
|
9
|
+
APIResponseValidationError,
|
|
10
|
+
APIStatusError,
|
|
11
|
+
APITimeoutError,
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
BadRequestError,
|
|
14
|
+
Cosplay2Error,
|
|
15
|
+
InternalServerError,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
PermissionDeniedError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"APIConnectionError",
|
|
23
|
+
"APIError",
|
|
24
|
+
"APIResponseValidationError",
|
|
25
|
+
"APIStatusError",
|
|
26
|
+
"APITimeoutError",
|
|
27
|
+
"AsyncCosplay2",
|
|
28
|
+
"AuthenticationError",
|
|
29
|
+
"BadRequestError",
|
|
30
|
+
"Cosplay2",
|
|
31
|
+
"Cosplay2Error",
|
|
32
|
+
"InternalServerError",
|
|
33
|
+
"NotFoundError",
|
|
34
|
+
"PermissionDeniedError",
|
|
35
|
+
"RateLimitError",
|
|
36
|
+
"__version__",
|
|
37
|
+
"models",
|
|
38
|
+
]
|