ctftime-api 0.1.1__tar.gz → 0.1.2__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.2}/PKG-INFO +2 -2
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/client.py +35 -33
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/pyproject.toml +2 -2
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/LICENSE +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/README.md +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/__init__.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/__init__.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/duration.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/event.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/rating.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/team.py +0 -0
- {ctftime_api-0.1.1 → ctftime_api-0.1.2}/ctftime_api/models/vote.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.2
|
|
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
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
import httpx
|
|
4
5
|
from httpx import URL, Timeout
|
|
@@ -9,7 +10,6 @@ from ctftime_api.models.vote import Vote
|
|
|
9
10
|
from ctftime_api.models.event import Event, EventResult
|
|
10
11
|
from ctftime_api.models.team import TeamRank, Team, TeamComplete
|
|
11
12
|
|
|
12
|
-
|
|
13
13
|
__all__ = ["CTFTimeClient"]
|
|
14
14
|
|
|
15
15
|
|
|
@@ -27,6 +27,18 @@ class CTFTimeClient:
|
|
|
27
27
|
self._client = httpx.AsyncClient(*args, **kwargs)
|
|
28
28
|
self._base_url = URL("https://ctftime.org/api/v1/")
|
|
29
29
|
|
|
30
|
+
async def _get(self, url: str | URL, **kwargs) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Perform a GET request.
|
|
33
|
+
:param url: The url to make the request to.
|
|
34
|
+
:param kwargs: Additional arguments to pass to the request.
|
|
35
|
+
:return: The response JSON.
|
|
36
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
37
|
+
"""
|
|
38
|
+
response = await self._client.get(url, **kwargs)
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
return response.json()
|
|
41
|
+
|
|
30
42
|
async def close(self):
|
|
31
43
|
"""Close the underlying httpx.AsyncClient."""
|
|
32
44
|
await self._client.aclose()
|
|
@@ -39,16 +51,16 @@ class CTFTimeClient:
|
|
|
39
51
|
:param year: The year to get the top teams for. If None, the current year will be used.
|
|
40
52
|
:param limit: The number of teams to get.
|
|
41
53
|
:return: A list of the top teams.
|
|
54
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
42
55
|
"""
|
|
43
56
|
if year is None:
|
|
44
57
|
url = self._base_url.join("top/")
|
|
45
58
|
year = datetime.now().year
|
|
46
59
|
else:
|
|
47
60
|
url = self._base_url.join(f"top/{year}/")
|
|
48
|
-
response = await self._client.get(url, params={"limit": limit})
|
|
49
|
-
response.raise_for_status()
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
response: dict[str, list[dict]] = await self._get(url, params={"limit": limit})
|
|
63
|
+
teams = response.get(f"{year}", [])
|
|
52
64
|
|
|
53
65
|
return [TeamRank.model_validate(team) for team in teams]
|
|
54
66
|
|
|
@@ -60,6 +72,8 @@ class CTFTimeClient:
|
|
|
60
72
|
:param country: The country to get the top teams for.
|
|
61
73
|
It can be a pycountry Country object or a two-letter country code.
|
|
62
74
|
:return: A list of the top teams.
|
|
75
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
76
|
+
:raise ValueError: If the country is not a two-letter country code or a pycountry Country object.
|
|
63
77
|
"""
|
|
64
78
|
if isinstance(country, CountryAlpha2):
|
|
65
79
|
country = country
|
|
@@ -70,10 +84,7 @@ class CTFTimeClient:
|
|
|
70
84
|
)
|
|
71
85
|
|
|
72
86
|
url = self._base_url.join("top-by-country/").join(f"{country}/")
|
|
73
|
-
|
|
74
|
-
response.raise_for_status()
|
|
75
|
-
|
|
76
|
-
teams = response.json()
|
|
87
|
+
teams: list[dict[str, Any]] = await self._get(url)
|
|
77
88
|
|
|
78
89
|
return [TeamRank.model_validate(team) for team in teams]
|
|
79
90
|
|
|
@@ -88,6 +99,8 @@ class CTFTimeClient:
|
|
|
88
99
|
It can be a Unix timestamp or a datetime object.
|
|
89
100
|
:param limit: The number of events to get.
|
|
90
101
|
:return: A list of events.
|
|
102
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
103
|
+
:raise ValueError: If the start date is after the end date.
|
|
91
104
|
"""
|
|
92
105
|
if isinstance(start, datetime):
|
|
93
106
|
start = int(start.timestamp())
|
|
@@ -98,12 +111,9 @@ class CTFTimeClient:
|
|
|
98
111
|
raise ValueError("The start date must be before the end date.")
|
|
99
112
|
|
|
100
113
|
url = self._base_url.join("events/")
|
|
101
|
-
|
|
114
|
+
events: list[dict[str, Any]] = await self._get(
|
|
102
115
|
url, params={"start": start, "finish": end, "limit": limit}
|
|
103
116
|
)
|
|
104
|
-
response.raise_for_status()
|
|
105
|
-
|
|
106
|
-
events = response.json()
|
|
107
117
|
|
|
108
118
|
return [Event.model_validate(event) for event in events]
|
|
109
119
|
|
|
@@ -112,12 +122,10 @@ class CTFTimeClient:
|
|
|
112
122
|
Get information about a specific event.
|
|
113
123
|
:param event_id: The ID of the event.
|
|
114
124
|
:return: The event information.
|
|
125
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
115
126
|
"""
|
|
116
127
|
url = self._base_url.join(f"events/{event_id}/")
|
|
117
|
-
|
|
118
|
-
response.raise_for_status()
|
|
119
|
-
|
|
120
|
-
event = response.json()
|
|
128
|
+
event: dict[str, Any] = await self._get(url)
|
|
121
129
|
|
|
122
130
|
return Event.model_validate(event)
|
|
123
131
|
|
|
@@ -129,14 +137,14 @@ class CTFTimeClient:
|
|
|
129
137
|
:param limit: The number of teams to get.
|
|
130
138
|
:param offset: The offset to start from.
|
|
131
139
|
:return: A list of teams.
|
|
140
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
132
141
|
"""
|
|
133
142
|
url = self._base_url.join("teams/")
|
|
134
|
-
|
|
143
|
+
|
|
144
|
+
response: dict[str, Any] = await self._get(
|
|
135
145
|
url, params={"limit": limit, "offset": offset}
|
|
136
146
|
)
|
|
137
|
-
response.
|
|
138
|
-
|
|
139
|
-
teams = response.json().get("results", [])
|
|
147
|
+
teams: list[dict[str, Any]] = response.get("results", [])
|
|
140
148
|
|
|
141
149
|
return [Team.model_validate(team) for team in teams]
|
|
142
150
|
|
|
@@ -145,12 +153,10 @@ class CTFTimeClient:
|
|
|
145
153
|
Get information about a specific team.
|
|
146
154
|
:param team_id: The ID of the team.
|
|
147
155
|
:return: The team information.
|
|
156
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
148
157
|
"""
|
|
149
158
|
url = self._base_url.join(f"teams/{team_id}/")
|
|
150
|
-
|
|
151
|
-
response.raise_for_status()
|
|
152
|
-
|
|
153
|
-
team = response.json()
|
|
159
|
+
team: dict[str, Any] = await self._get(url)
|
|
154
160
|
|
|
155
161
|
return TeamComplete.model_validate(team)
|
|
156
162
|
|
|
@@ -162,19 +168,17 @@ class CTFTimeClient:
|
|
|
162
168
|
:param year: The year to get the results for.
|
|
163
169
|
If None, the current year will be used.
|
|
164
170
|
:return: A dictionary of event results.
|
|
171
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
165
172
|
"""
|
|
166
173
|
if year is None:
|
|
167
174
|
url = self._base_url.join("results/")
|
|
168
175
|
else:
|
|
169
176
|
url = self._base_url.join(f"results/{year}/")
|
|
170
177
|
|
|
171
|
-
|
|
172
|
-
response.raise_for_status()
|
|
173
|
-
|
|
174
|
-
event = response.json()
|
|
178
|
+
event: dict[str, dict] = await self._get(url)
|
|
175
179
|
|
|
176
180
|
return {
|
|
177
|
-
int(ctf_id): EventResult(**result, ctf_id=ctf_id)
|
|
181
|
+
int(ctf_id): EventResult(**result, ctf_id=int(ctf_id))
|
|
178
182
|
for ctf_id, result in event.items()
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -188,6 +192,7 @@ class CTFTimeClient:
|
|
|
188
192
|
:param timeout: The timeout for the request.
|
|
189
193
|
If None, the session timeout will be used.
|
|
190
194
|
:return: A list of votes.
|
|
195
|
+
:raise httpx.HTTPStatusError: If the response status code is not successful.
|
|
191
196
|
"""
|
|
192
197
|
if year is None:
|
|
193
198
|
year = datetime.now().year
|
|
@@ -196,9 +201,6 @@ class CTFTimeClient:
|
|
|
196
201
|
timeout = self._client.timeout
|
|
197
202
|
|
|
198
203
|
url = self._base_url.join(f"votes/{year}/")
|
|
199
|
-
|
|
200
|
-
response.raise_for_status()
|
|
201
|
-
|
|
202
|
-
votes = response.json()
|
|
204
|
+
votes: list[dict] = await self._get(url, timeout=timeout)
|
|
203
205
|
|
|
204
206
|
return [Vote(**vote) for vote in votes]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ctftime_api"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
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",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|