ctftime-api 0.1.0__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.
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ctftime_api
3
- Version: 0.1.0
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 :: 3 - Alpha
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
+ Classifier: Framework :: Pydantic
13
+ Classifier: Operating System :: OS Independent
12
14
  Classifier: Programming Language :: Python :: 3
13
15
  Classifier: Programming Language :: Python :: 3.11
14
16
  Classifier: Programming Language :: Python :: 3.12
@@ -1,14 +1,15 @@
1
1
  from datetime import datetime
2
+ from typing import Any
2
3
 
3
4
  import httpx
4
- from httpx import URL
5
+ from httpx import URL, Timeout
5
6
 
6
7
  from pydantic_extra_types.country import CountryAlpha2
7
8
 
9
+ from ctftime_api.models.vote import Vote
8
10
  from ctftime_api.models.event import Event, EventResult
9
11
  from ctftime_api.models.team import TeamRank, Team, TeamComplete
10
12
 
11
-
12
13
  __all__ = ["CTFTimeClient"]
13
14
 
14
15
 
@@ -26,6 +27,18 @@ class CTFTimeClient:
26
27
  self._client = httpx.AsyncClient(*args, **kwargs)
27
28
  self._base_url = URL("https://ctftime.org/api/v1/")
28
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
+
29
42
  async def close(self):
30
43
  """Close the underlying httpx.AsyncClient."""
31
44
  await self._client.aclose()
@@ -38,16 +51,16 @@ class CTFTimeClient:
38
51
  :param year: The year to get the top teams for. If None, the current year will be used.
39
52
  :param limit: The number of teams to get.
40
53
  :return: A list of the top teams.
54
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
41
55
  """
42
56
  if year is None:
43
57
  url = self._base_url.join("top/")
44
58
  year = datetime.now().year
45
59
  else:
46
60
  url = self._base_url.join(f"top/{year}/")
47
- response = await self._client.get(url, params={"limit": limit})
48
- response.raise_for_status()
49
61
 
50
- teams = response.json().get(f"{year}", [])
62
+ response: dict[str, list[dict]] = await self._get(url, params={"limit": limit})
63
+ teams = response.get(f"{year}", [])
51
64
 
52
65
  return [TeamRank.model_validate(team) for team in teams]
53
66
 
@@ -59,6 +72,8 @@ class CTFTimeClient:
59
72
  :param country: The country to get the top teams for.
60
73
  It can be a pycountry Country object or a two-letter country code.
61
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.
62
77
  """
63
78
  if isinstance(country, CountryAlpha2):
64
79
  country = country
@@ -69,10 +84,7 @@ class CTFTimeClient:
69
84
  )
70
85
 
71
86
  url = self._base_url.join("top-by-country/").join(f"{country}/")
72
- response = await self._client.get(url)
73
- response.raise_for_status()
74
-
75
- teams = response.json()
87
+ teams: list[dict[str, Any]] = await self._get(url)
76
88
 
77
89
  return [TeamRank.model_validate(team) for team in teams]
78
90
 
@@ -87,6 +99,8 @@ class CTFTimeClient:
87
99
  It can be a Unix timestamp or a datetime object.
88
100
  :param limit: The number of events to get.
89
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.
90
104
  """
91
105
  if isinstance(start, datetime):
92
106
  start = int(start.timestamp())
@@ -97,12 +111,9 @@ class CTFTimeClient:
97
111
  raise ValueError("The start date must be before the end date.")
98
112
 
99
113
  url = self._base_url.join("events/")
100
- response = await self._client.get(
114
+ events: list[dict[str, Any]] = await self._get(
101
115
  url, params={"start": start, "finish": end, "limit": limit}
102
116
  )
103
- response.raise_for_status()
104
-
105
- events = response.json()
106
117
 
107
118
  return [Event.model_validate(event) for event in events]
108
119
 
@@ -111,12 +122,10 @@ class CTFTimeClient:
111
122
  Get information about a specific event.
112
123
  :param event_id: The ID of the event.
113
124
  :return: The event information.
125
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
114
126
  """
115
127
  url = self._base_url.join(f"events/{event_id}/")
116
- response = await self._client.get(url)
117
- response.raise_for_status()
118
-
119
- event = response.json()
128
+ event: dict[str, Any] = await self._get(url)
120
129
 
121
130
  return Event.model_validate(event)
122
131
 
@@ -128,14 +137,14 @@ class CTFTimeClient:
128
137
  :param limit: The number of teams to get.
129
138
  :param offset: The offset to start from.
130
139
  :return: A list of teams.
140
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
131
141
  """
132
142
  url = self._base_url.join("teams/")
133
- response = await self._client.get(
143
+
144
+ response: dict[str, Any] = await self._get(
134
145
  url, params={"limit": limit, "offset": offset}
135
146
  )
136
- response.raise_for_status()
137
-
138
- teams = response.json().get("results", [])
147
+ teams: list[dict[str, Any]] = response.get("results", [])
139
148
 
140
149
  return [Team.model_validate(team) for team in teams]
141
150
 
@@ -144,12 +153,10 @@ class CTFTimeClient:
144
153
  Get information about a specific team.
145
154
  :param team_id: The ID of the team.
146
155
  :return: The team information.
156
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
147
157
  """
148
158
  url = self._base_url.join(f"teams/{team_id}/")
149
- response = await self._client.get(url)
150
- response.raise_for_status()
151
-
152
- team = response.json()
159
+ team: dict[str, Any] = await self._get(url)
153
160
 
154
161
  return TeamComplete.model_validate(team)
155
162
 
@@ -161,18 +168,39 @@ class CTFTimeClient:
161
168
  :param year: The year to get the results for.
162
169
  If None, the current year will be used.
163
170
  :return: A dictionary of event results.
171
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
164
172
  """
165
173
  if year is None:
166
174
  url = self._base_url.join("results/")
167
175
  else:
168
176
  url = self._base_url.join(f"results/{year}/")
169
177
 
170
- response = await self._client.get(url)
171
- response.raise_for_status()
172
-
173
- event = response.json()
178
+ event: dict[str, dict] = await self._get(url)
174
179
 
175
180
  return {
176
- int(ctf_id): EventResult(**result, ctf_id=ctf_id)
181
+ int(ctf_id): EventResult(**result, ctf_id=int(ctf_id))
177
182
  for ctf_id, result in event.items()
178
183
  }
184
+
185
+ async def get_votes_per_year(
186
+ self, year: int | None, timeout: Timeout | int | float | None = None
187
+ ) -> list[Vote]:
188
+ """
189
+ Get the votes for a specific year.
190
+ This API call may take a long time to complete.
191
+ :param year: The year to get the votes for or None for the current year.
192
+ :param timeout: The timeout for the request.
193
+ If None, the session timeout will be used.
194
+ :return: A list of votes.
195
+ :raise httpx.HTTPStatusError: If the response status code is not successful.
196
+ """
197
+ if year is None:
198
+ year = datetime.now().year
199
+
200
+ if timeout is None:
201
+ timeout = self._client.timeout
202
+
203
+ url = self._base_url.join(f"votes/{year}/")
204
+ votes: list[dict] = await self._get(url, timeout=timeout)
205
+
206
+ return [Vote(**vote) for vote in votes]
@@ -0,0 +1,14 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ __all__ = ["Vote"]
7
+
8
+
9
+ class Vote(BaseModel):
10
+ event_id: int
11
+ user_id: int
12
+ user_teams: list[int] # list of team IDs
13
+ weight: str
14
+ creation_date: datetime
@@ -1,15 +1,17 @@
1
1
  [project]
2
2
  name = "ctftime_api"
3
- version = "0.1.0"
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 :: 3 - Alpha",
10
+ "Development Status :: 4 - Beta",
11
11
  "Intended Audience :: Developers",
12
12
  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
13
+ "Framework :: Pydantic",
14
+ "Operating System :: OS Independent",
13
15
  "Programming Language :: Python :: 3",
14
16
  "Programming Language :: Python :: 3.11",
15
17
  "Programming Language :: Python :: 3.12",
File without changes
File without changes