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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ctftime_api
3
- Version: 0.1.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 :: 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
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, *args, **kwargs):
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
- self._client = httpx.AsyncClient(*args, **kwargs)
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
- teams = response.json().get(f"{year}", [])
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
- response = await self._client.get(url)
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
- response = await self._client.get(
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
- response = await self._client.get(url)
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
- response = await self._client.get(
146
+
147
+ response: dict[str, Any] = await self._get(
135
148
  url, params={"limit": limit, "offset": offset}
136
149
  )
137
- response.raise_for_status()
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
- response = await self._client.get(url)
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
- response = await self._client.get(url)
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
- response = await self._client.get(url, timeout=timeout)
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 .duration import Duration
4
+ from .team import Team, TeamComplete, BaseTeam, TeamRank, TeamResult
@@ -1,6 +1,5 @@
1
1
  from pydantic import BaseModel
2
2
 
3
-
4
3
  __all__ = ["Duration"]
5
4
 
6
5
 
@@ -1,6 +1,6 @@
1
- from pydantic import BaseModel, HttpUrl
2
1
  from datetime import datetime
3
2
 
3
+ from pydantic import BaseModel, HttpUrl
4
4
 
5
5
  from .duration import Duration
6
6
  from .team import BaseTeam, TeamResult
@@ -5,7 +5,6 @@ from pydantic_extra_types.country import CountryAlpha2
5
5
 
6
6
  from .rating import Rating
7
7
 
8
-
9
8
  __all__ = ["BaseTeam", "Team", "TeamRank", "TeamComplete", "TeamResult"]
10
9
 
11
10
 
@@ -2,7 +2,6 @@ from datetime import datetime
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
5
-
6
5
  __all__ = ["Vote"]
7
6
 
8
7
 
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "ctftime_api"
3
- version = "0.1.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 :: 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
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