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.
@@ -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
+ ]