ctftime-api 0.1.1__tar.gz → 0.1.3__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.
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/PKG-INFO +8 -2
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/client.py +43 -38
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/__init__.py +2 -2
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/duration.py +0 -1
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/event.py +1 -1
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/team.py +0 -1
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/vote.py +0 -1
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/pyproject.toml +11 -2
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/LICENSE +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/README.md +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/__init__.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.3}/ctftime_api/models/rating.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ctftime_api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: CTFTime API wrapper
|
|
5
5
|
Keywords: ctftime,api,wrapper
|
|
6
6
|
Author: John Toniutti
|
|
7
7
|
Author-email: john.toniutti@gmail.com
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
12
12
|
Classifier: Framework :: Pydantic
|
|
@@ -18,10 +18,16 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
|
+
Provides-Extra: speed
|
|
22
|
+
Requires-Dist: brotli (>=1.1.0,<2.0.0) ; (platform_python_implementation == "CPython") and (extra == "speed")
|
|
23
|
+
Requires-Dist: brotlicffi (>=1.1.0.0,<2.0.0.0) ; (platform_python_implementation != "CPython") and (extra == "speed")
|
|
24
|
+
Requires-Dist: h2 (>=4.2.0,<5.0.0) ; extra == "speed"
|
|
21
25
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
22
26
|
Requires-Dist: pycountry (>=24.6.1,<25.0.0)
|
|
23
27
|
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
|
24
28
|
Requires-Dist: pydantic-extra-types (>=2.10.2,<3.0.0)
|
|
29
|
+
Requires-Dist: uvloop (>=0.21.0,<1.0.0) ; (sys_platform != "win32") and (extra == "speed")
|
|
30
|
+
Requires-Dist: zstandard (>=0.23.0,<0.24.0) ; extra == "speed"
|
|
25
31
|
Project-URL: Documentation, https://jotonedev.github.io/ctftime_api/
|
|
26
32
|
Project-URL: Repository, https://github.com/jotonedev/ctftime_api
|
|
27
33
|
Description-Content-Type: text/markdown
|
|
@@ -1,32 +1,47 @@
|
|
|
1
|
+
import importlib.util
|
|
1
2
|
from datetime import datetime
|
|
3
|
+
from typing import Any
|
|
2
4
|
|
|
3
5
|
import httpx
|
|
4
6
|
from httpx import URL, Timeout
|
|
5
|
-
|
|
6
7
|
from pydantic_extra_types.country import CountryAlpha2
|
|
7
8
|
|
|
8
|
-
from ctftime_api.models.vote import Vote
|
|
9
9
|
from ctftime_api.models.event import Event, EventResult
|
|
10
10
|
from ctftime_api.models.team import TeamRank, Team, TeamComplete
|
|
11
|
-
|
|
11
|
+
from ctftime_api.models.vote import Vote
|
|
12
12
|
|
|
13
13
|
__all__ = ["CTFTimeClient"]
|
|
14
14
|
|
|
15
|
+
h2 = importlib.util.find_spec("httpcore.h2")
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
class CTFTimeClient:
|
|
17
|
-
def __init__(self, client: httpx.AsyncClient | None = None,
|
|
19
|
+
def __init__(self, client: httpx.AsyncClient | None = None, **kwargs):
|
|
18
20
|
"""
|
|
19
21
|
Initialize the CTFTime API client.
|
|
20
22
|
:param client: The httpx.AsyncClient to use. If None, a new client will be created.
|
|
21
|
-
:param args: Args that will be passed to the httpx.AsyncClient constructor.
|
|
22
23
|
:param kwargs: Kwargs that will be passed to the httpx.AsyncClient constructor.
|
|
23
24
|
"""
|
|
24
25
|
if client is not None:
|
|
25
26
|
self._client = client
|
|
26
27
|
else:
|
|
27
|
-
|
|
28
|
+
if h2 is not None:
|
|
29
|
+
kwargs.setdefault("http2", True)
|
|
30
|
+
self._client = httpx.AsyncClient(**kwargs)
|
|
28
31
|
self._base_url = URL("https://ctftime.org/api/v1/")
|
|
29
32
|
|
|
33
|
+
async def _get(self, url: str | URL, **kwargs) -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Perform a GET request.
|
|
36
|
+
:param url: The url to make the request to.
|
|
37
|
+
:param kwargs: Additional arguments to pass to the request.
|
|
38
|
+
:return: The response JSON.
|
|
39
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
40
|
+
"""
|
|
41
|
+
response = await self._client.get(url, **kwargs)
|
|
42
|
+
response.raise_for_status()
|
|
43
|
+
return response.json()
|
|
44
|
+
|
|
30
45
|
async def close(self):
|
|
31
46
|
"""Close the underlying httpx.AsyncClient."""
|
|
32
47
|
await self._client.aclose()
|
|
@@ -39,16 +54,16 @@ class CTFTimeClient:
|
|
|
39
54
|
:param year: The year to get the top teams for. If None, the current year will be used.
|
|
40
55
|
:param limit: The number of teams to get.
|
|
41
56
|
:return: A list of the top teams.
|
|
57
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
42
58
|
"""
|
|
43
59
|
if year is None:
|
|
44
60
|
url = self._base_url.join("top/")
|
|
45
61
|
year = datetime.now().year
|
|
46
62
|
else:
|
|
47
63
|
url = self._base_url.join(f"top/{year}/")
|
|
48
|
-
response = await self._client.get(url, params={"limit": limit})
|
|
49
|
-
response.raise_for_status()
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
response: dict[str, list[dict]] = await self._get(url, params={"limit": limit})
|
|
66
|
+
teams = response.get(f"{year}", [])
|
|
52
67
|
|
|
53
68
|
return [TeamRank.model_validate(team) for team in teams]
|
|
54
69
|
|
|
@@ -60,6 +75,8 @@ class CTFTimeClient:
|
|
|
60
75
|
:param country: The country to get the top teams for.
|
|
61
76
|
It can be a pycountry Country object or a two-letter country code.
|
|
62
77
|
:return: A list of the top teams.
|
|
78
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
79
|
+
:raise ValueError: If the country is not a two-letter country code or a pycountry Country object.
|
|
63
80
|
"""
|
|
64
81
|
if isinstance(country, CountryAlpha2):
|
|
65
82
|
country = country
|
|
@@ -70,10 +87,7 @@ class CTFTimeClient:
|
|
|
70
87
|
)
|
|
71
88
|
|
|
72
89
|
url = self._base_url.join("top-by-country/").join(f"{country}/")
|
|
73
|
-
|
|
74
|
-
response.raise_for_status()
|
|
75
|
-
|
|
76
|
-
teams = response.json()
|
|
90
|
+
teams: list[dict[str, Any]] = await self._get(url)
|
|
77
91
|
|
|
78
92
|
return [TeamRank.model_validate(team) for team in teams]
|
|
79
93
|
|
|
@@ -88,6 +102,8 @@ class CTFTimeClient:
|
|
|
88
102
|
It can be a Unix timestamp or a datetime object.
|
|
89
103
|
:param limit: The number of events to get.
|
|
90
104
|
:return: A list of events.
|
|
105
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
106
|
+
:raise ValueError: If the start date is after the end date.
|
|
91
107
|
"""
|
|
92
108
|
if isinstance(start, datetime):
|
|
93
109
|
start = int(start.timestamp())
|
|
@@ -98,12 +114,9 @@ class CTFTimeClient:
|
|
|
98
114
|
raise ValueError("The start date must be before the end date.")
|
|
99
115
|
|
|
100
116
|
url = self._base_url.join("events/")
|
|
101
|
-
|
|
117
|
+
events: list[dict[str, Any]] = await self._get(
|
|
102
118
|
url, params={"start": start, "finish": end, "limit": limit}
|
|
103
119
|
)
|
|
104
|
-
response.raise_for_status()
|
|
105
|
-
|
|
106
|
-
events = response.json()
|
|
107
120
|
|
|
108
121
|
return [Event.model_validate(event) for event in events]
|
|
109
122
|
|
|
@@ -112,12 +125,10 @@ class CTFTimeClient:
|
|
|
112
125
|
Get information about a specific event.
|
|
113
126
|
:param event_id: The ID of the event.
|
|
114
127
|
:return: The event information.
|
|
128
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
115
129
|
"""
|
|
116
130
|
url = self._base_url.join(f"events/{event_id}/")
|
|
117
|
-
|
|
118
|
-
response.raise_for_status()
|
|
119
|
-
|
|
120
|
-
event = response.json()
|
|
131
|
+
event: dict[str, Any] = await self._get(url)
|
|
121
132
|
|
|
122
133
|
return Event.model_validate(event)
|
|
123
134
|
|
|
@@ -129,14 +140,14 @@ class CTFTimeClient:
|
|
|
129
140
|
:param limit: The number of teams to get.
|
|
130
141
|
:param offset: The offset to start from.
|
|
131
142
|
:return: A list of teams.
|
|
143
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
132
144
|
"""
|
|
133
145
|
url = self._base_url.join("teams/")
|
|
134
|
-
|
|
146
|
+
|
|
147
|
+
response: dict[str, Any] = await self._get(
|
|
135
148
|
url, params={"limit": limit, "offset": offset}
|
|
136
149
|
)
|
|
137
|
-
response.
|
|
138
|
-
|
|
139
|
-
teams = response.json().get("results", [])
|
|
150
|
+
teams: list[dict[str, Any]] = response.get("results", [])
|
|
140
151
|
|
|
141
152
|
return [Team.model_validate(team) for team in teams]
|
|
142
153
|
|
|
@@ -145,12 +156,10 @@ class CTFTimeClient:
|
|
|
145
156
|
Get information about a specific team.
|
|
146
157
|
:param team_id: The ID of the team.
|
|
147
158
|
:return: The team information.
|
|
159
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
148
160
|
"""
|
|
149
161
|
url = self._base_url.join(f"teams/{team_id}/")
|
|
150
|
-
|
|
151
|
-
response.raise_for_status()
|
|
152
|
-
|
|
153
|
-
team = response.json()
|
|
162
|
+
team: dict[str, Any] = await self._get(url)
|
|
154
163
|
|
|
155
164
|
return TeamComplete.model_validate(team)
|
|
156
165
|
|
|
@@ -162,19 +171,17 @@ class CTFTimeClient:
|
|
|
162
171
|
:param year: The year to get the results for.
|
|
163
172
|
If None, the current year will be used.
|
|
164
173
|
:return: A dictionary of event results.
|
|
174
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
165
175
|
"""
|
|
166
176
|
if year is None:
|
|
167
177
|
url = self._base_url.join("results/")
|
|
168
178
|
else:
|
|
169
179
|
url = self._base_url.join(f"results/{year}/")
|
|
170
180
|
|
|
171
|
-
|
|
172
|
-
response.raise_for_status()
|
|
173
|
-
|
|
174
|
-
event = response.json()
|
|
181
|
+
event: dict[str, dict] = await self._get(url)
|
|
175
182
|
|
|
176
183
|
return {
|
|
177
|
-
int(ctf_id): EventResult(**result, ctf_id=ctf_id)
|
|
184
|
+
int(ctf_id): EventResult(**result, ctf_id=int(ctf_id))
|
|
178
185
|
for ctf_id, result in event.items()
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -188,6 +195,7 @@ class CTFTimeClient:
|
|
|
188
195
|
:param timeout: The timeout for the request.
|
|
189
196
|
If None, the session timeout will be used.
|
|
190
197
|
:return: A list of votes.
|
|
198
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
191
199
|
"""
|
|
192
200
|
if year is None:
|
|
193
201
|
year = datetime.now().year
|
|
@@ -196,9 +204,6 @@ class CTFTimeClient:
|
|
|
196
204
|
timeout = self._client.timeout
|
|
197
205
|
|
|
198
206
|
url = self._base_url.join(f"votes/{year}/")
|
|
199
|
-
|
|
200
|
-
response.raise_for_status()
|
|
201
|
-
|
|
202
|
-
votes = response.json()
|
|
207
|
+
votes: list[dict] = await self._get(url, timeout=timeout)
|
|
203
208
|
|
|
204
209
|
return [Vote(**vote) for vote in votes]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
from .duration import Duration
|
|
1
2
|
from .event import Event, EventResult
|
|
2
|
-
from .team import Team, TeamComplete, BaseTeam, TeamRank, TeamResult
|
|
3
3
|
from .rating import Rating
|
|
4
|
-
from .
|
|
4
|
+
from .team import Team, TeamComplete, BaseTeam, TeamRank, TeamResult
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctftime_api"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "CTFTime API wrapper"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "John Toniutti",email = "john.toniutti@gmail.com"}
|
|
7
7
|
]
|
|
8
8
|
keywords = ["ctftime", "api", "wrapper"]
|
|
9
9
|
classifiers = [
|
|
10
|
-
"Development Status ::
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
11
|
"Intended Audience :: Developers",
|
|
12
12
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
13
13
|
"Framework :: Pydantic",
|
|
@@ -29,6 +29,15 @@ dependencies = [
|
|
|
29
29
|
"pycountry (>=24.6.1,<25.0.0)",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
speed = [
|
|
34
|
+
"uvloop (>=0.21.0,<1.0.0) ; sys_platform != 'win32'",
|
|
35
|
+
"brotli (>=1.1.0,<2.0.0) ; platform_python_implementation == 'CPython' ",
|
|
36
|
+
"brotlicffi (>=1.1.0.0,<2.0.0.0) ; platform_python_implementation != 'CPython' ",
|
|
37
|
+
"h2 (>=4.2.0,<5.0.0)",
|
|
38
|
+
"zstandard (>=0.23.0,<0.24.0)",
|
|
39
|
+
]
|
|
40
|
+
|
|
32
41
|
[project.urls]
|
|
33
42
|
repository = "https://github.com/jotonedev/ctftime_api"
|
|
34
43
|
documentation = "https://jotonedev.github.io/ctftime_api/"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|