ctftime-api 0.1.6__tar.gz → 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.
Files changed (47) hide show
  1. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/workflows/codeql.yml +3 -3
  2. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/workflows/docs.yml +3 -3
  3. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/workflows/publish.yml +5 -5
  4. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/workflows/test.yml +2 -2
  5. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/PKG-INFO +11 -12
  6. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/README.md +2 -0
  7. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/ctftime_api/client.py +16 -19
  8. ctftime_api-0.3.0/ctftime_api/models/country.py +259 -0
  9. ctftime_api-0.3.0/ctftime_api/models/duration.py +12 -0
  10. ctftime_api-0.3.0/ctftime_api/models/event.py +53 -0
  11. ctftime_api-0.3.0/ctftime_api/models/rating.py +16 -0
  12. ctftime_api-0.3.0/ctftime_api/models/team.py +61 -0
  13. ctftime_api-0.3.0/ctftime_api/models/vote.py +16 -0
  14. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/pyproject.toml +12 -14
  15. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/tests/test_client.py +16 -15
  16. ctftime_api-0.3.0/uv.lock +2354 -0
  17. ctftime_api-0.1.6/ctftime_api/models/duration.py +0 -8
  18. ctftime_api-0.1.6/ctftime_api/models/event.py +0 -42
  19. ctftime_api-0.1.6/ctftime_api/models/rating.py +0 -12
  20. ctftime_api-0.1.6/ctftime_api/models/team.py +0 -50
  21. ctftime_api-0.1.6/ctftime_api/models/vote.py +0 -13
  22. ctftime_api-0.1.6/uv.lock +0 -1492
  23. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/dependabot.yml +0 -0
  24. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.github/workflows/cleanup.yml +0 -0
  25. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/.gitignore +0 -0
  26. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/LICENSE +0 -0
  27. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/ctftime_api/__init__.py +0 -0
  28. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/ctftime_api/models/__init__.py +0 -0
  29. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/ctftime_api/py.typed +0 -0
  30. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/client.md +0 -0
  31. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/models/duration.md +0 -0
  32. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/models/event.md +0 -0
  33. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/models/rating.md +0 -0
  34. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/models/team.md +0 -0
  35. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/api/models/vote.md +0 -0
  36. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/inter/italic.woff2 +0 -0
  37. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/inter/italic_ext.woff2 +0 -0
  38. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/inter/normal.woff2 +0 -0
  39. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/inter/normal_ext.woff2 +0 -0
  40. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/jetbrains_mono/normal.woff2 +0 -0
  41. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/assets/fonts/jetbrains_mono/normal_ext.woff2 +0 -0
  42. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/index.md +0 -0
  43. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/docs/stylesheets/fonts.css +0 -0
  44. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/examples/test_methods/main.py +0 -0
  45. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/justfile +0 -0
  46. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/mkdocs.yml +0 -0
  47. {ctftime_api-0.1.6 → ctftime_api-0.3.0}/tests/__init__.py +0 -0
@@ -21,17 +21,17 @@ jobs:
21
21
  build-mode: none
22
22
  steps:
23
23
  - name: Checkout repository
24
- uses: actions/checkout@v4
24
+ uses: actions/checkout@v6
25
25
 
26
26
  # Initializes the CodeQL tools for scanning.
27
27
  - name: Initialize CodeQL
28
- uses: github/codeql-action/init@v3
28
+ uses: github/codeql-action/init@v4
29
29
  with:
30
30
  languages: ${{ matrix.language }}
31
31
  build-mode: ${{ matrix.build-mode }}
32
32
  queries: security-extended,security-and-quality
33
33
 
34
34
  - name: Perform CodeQL Analysis
35
- uses: github/codeql-action/analyze@v3
35
+ uses: github/codeql-action/analyze@v4
36
36
  with:
37
37
  category: "/language:${{matrix.language}}"
@@ -21,10 +21,10 @@ jobs:
21
21
 
22
22
  steps:
23
23
  - name: Checkout code
24
- uses: actions/checkout@v4
24
+ uses: actions/checkout@v6
25
25
 
26
26
  - name: Install uv
27
- uses: astral-sh/setup-uv@v6
27
+ uses: astral-sh/setup-uv@v7
28
28
  with:
29
29
  enable-cache: true
30
30
  cache-dependency-glob: "uv.lock"
@@ -46,7 +46,7 @@ jobs:
46
46
  - name: Upload docs
47
47
  if: ${{ (github.event.inputs.upload || github.ref_type == 'tag') }}
48
48
  id: deployment
49
- uses: actions/upload-pages-artifact@v3
49
+ uses: actions/upload-pages-artifact@v4
50
50
  with:
51
51
  path: site/
52
52
 
@@ -27,10 +27,10 @@ jobs:
27
27
 
28
28
  steps:
29
29
  - name: Checkout code
30
- uses: actions/checkout@v4
30
+ uses: actions/checkout@v6
31
31
 
32
32
  - name: Install uv
33
- uses: astral-sh/setup-uv@v6
33
+ uses: astral-sh/setup-uv@v7
34
34
  with:
35
35
  enable-cache: true
36
36
  cache-dependency-glob: "uv.lock"
@@ -50,13 +50,13 @@ jobs:
50
50
  - name: Sign build artifacts
51
51
  id: sign
52
52
  if: ${{ (github.event.inputs.upload || github.ref_type == 'tag') }}
53
- uses: actions/attest-build-provenance@v2
53
+ uses: actions/attest-build-provenance@v3
54
54
  with:
55
55
  subject-path: "dist/*"
56
56
 
57
57
  - name: Upload build artifacts
58
58
  id: upload
59
- uses: actions/upload-artifact@v4
59
+ uses: actions/upload-artifact@v6
60
60
  with:
61
61
  name: release-dists
62
62
  retention-days: 1
@@ -78,7 +78,7 @@ jobs:
78
78
  steps:
79
79
  - name: Retrieve release distributions
80
80
  id: download
81
- uses: actions/download-artifact@v4
81
+ uses: actions/download-artifact@v7
82
82
  with:
83
83
  name: release-dists
84
84
  path: dist/
@@ -22,10 +22,10 @@ jobs:
22
22
  contents: read
23
23
 
24
24
  steps:
25
- - uses: actions/checkout@v4
25
+ - uses: actions/checkout@v6
26
26
 
27
27
  - name: Install uv
28
- uses: astral-sh/setup-uv@v6
28
+ uses: astral-sh/setup-uv@v7
29
29
  with:
30
30
  enable-cache: true
31
31
  cache-dependency-glob: "uv.lock"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ctftime_api
3
- Version: 0.1.6
3
+ Version: 0.3.0
4
4
  Summary: CTFTime API wrapper
5
5
  Project-URL: repository, https://github.com/jotonedev/ctftime_api
6
6
  Project-URL: documentation, https://jotonedev.github.io/ctftime_api/
@@ -20,16 +20,13 @@ Classifier: Topic :: Internet :: WWW/HTTP
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.11
23
- Requires-Dist: httpx<0.29.0,>=0.28.1
24
- Requires-Dist: pycountry<25.0.0,>=24.6.1
25
- Requires-Dist: pydantic-extra-types<3.0.0,>=2.10.2
26
- Requires-Dist: pydantic<3.0.0,>=2.10.6
27
- Provides-Extra: speed
28
- Requires-Dist: brotli<2.0.0,>=1.1.0; (platform_python_implementation == 'CPython') and extra == 'speed'
29
- Requires-Dist: brotlicffi<2.0.0.0,>=1.1.0.0; (platform_python_implementation != 'CPython') and extra == 'speed'
30
- Requires-Dist: h2<5.0.0,>=4.2.0; extra == 'speed'
31
- Requires-Dist: uvloop<1.0.0,>=0.21.0; (sys_platform != 'win32') and extra == 'speed'
32
- Requires-Dist: zstandard<0.24.0,>=0.23.0; extra == 'speed'
23
+ Requires-Dist: dataclasses-json==0.6.7
24
+ Requires-Dist: httpx[http2]<0.29.0,>=0.28.1
25
+ Requires-Dist: yarl>=1.20.1
26
+ Provides-Extra: brotli
27
+ Requires-Dist: httpx[brotli]; extra == 'brotli'
28
+ Provides-Extra: zstd
29
+ Requires-Dist: httpx[zstd]; extra == 'zstd'
33
30
  Description-Content-Type: text/markdown
34
31
 
35
32
  # ctftime_api
@@ -63,6 +60,7 @@ Create a client to interact with the CTFTime API. For example, to get the top te
63
60
  import asyncio
64
61
  from ctftime_api.client import CTFTimeClient
65
62
 
63
+
66
64
  async def main():
67
65
  client = CTFTimeClient()
68
66
  top_teams = await client.get_top_teams_per_year()
@@ -70,6 +68,7 @@ async def main():
70
68
  print(f"{team.name} (ID: {team.id}) - Points: {team.points}")
71
69
  await client.close()
72
70
 
71
+
73
72
  asyncio.run(main())
74
73
  ```
75
74
 
@@ -29,6 +29,7 @@ Create a client to interact with the CTFTime API. For example, to get the top te
29
29
  import asyncio
30
30
  from ctftime_api.client import CTFTimeClient
31
31
 
32
+
32
33
  async def main():
33
34
  client = CTFTimeClient()
34
35
  top_teams = await client.get_top_teams_per_year()
@@ -36,6 +37,7 @@ async def main():
36
37
  print(f"{team.name} (ID: {team.id}) - Points: {team.points}")
37
38
  await client.close()
38
39
 
40
+
39
41
  asyncio.run(main())
40
42
  ```
41
43
 
@@ -4,10 +4,10 @@ from typing import Any
4
4
 
5
5
  import httpx
6
6
  from httpx import URL, Timeout
7
- from pydantic_extra_types.country import CountryAlpha2
8
7
 
8
+ from ctftime_api.models.country import CountryCode
9
9
  from ctftime_api.models.event import Event, EventResult
10
- from ctftime_api.models.team import TeamRank, Team, TeamComplete
10
+ from ctftime_api.models.team import Team, TeamComplete, TeamRank
11
11
  from ctftime_api.models.vote import Vote
12
12
 
13
13
  __all__ = ["CTFTimeClient"]
@@ -65,10 +65,10 @@ class CTFTimeClient:
65
65
  response: dict[str, list[dict]] = await self._get(url, params={"limit": limit})
66
66
  teams = response.get(f"{year}", [])
67
67
 
68
- return [TeamRank.model_validate(team) for team in teams]
68
+ return [TeamRank.from_dict(team) for team in teams]
69
69
 
70
70
  async def get_top_team_by_country(
71
- self, country: str | CountryAlpha2
71
+ self, country: str | CountryCode
72
72
  ) -> list[TeamRank]:
73
73
  """
74
74
  Get the top teams in the leaderboard for a specific country.
@@ -78,18 +78,15 @@ class CTFTimeClient:
78
78
  :raise httpx.HTTPStatusError: If the response status code is not successful.
79
79
  :raise ValueError: If the country is not a two-letter country code or a pycountry Country object.
80
80
  """
81
- if isinstance(country, CountryAlpha2):
82
- country = country
83
- elif isinstance(country, str):
84
- if len(country) != 2:
85
- raise ValueError(
86
- "Country must be a two-letter country code or a pycountry Country object."
87
- )
81
+ if isinstance(country, str) and len(country) != 2:
82
+ raise ValueError(
83
+ "Country must be a two-letter country code or a pycountry Country object."
84
+ )
88
85
 
89
86
  url = self._base_url.join("top-by-country/").join(f"{country}/")
90
87
  teams: list[dict[str, Any]] = await self._get(url)
91
88
 
92
- return [TeamRank.model_validate(team) for team in teams]
89
+ return [TeamRank.from_dict(team) for team in teams]
93
90
 
94
91
  async def get_events_information(
95
92
  self, start: int | datetime, end: int | datetime, limit: int = 10
@@ -118,7 +115,7 @@ class CTFTimeClient:
118
115
  url, params={"start": start, "finish": end, "limit": limit}
119
116
  )
120
117
 
121
- return [Event.model_validate(event) for event in events]
118
+ return [Event.from_dict(event) for event in events]
122
119
 
123
120
  async def get_event_information(self, event_id: int) -> Event:
124
121
  """
@@ -130,7 +127,7 @@ class CTFTimeClient:
130
127
  url = self._base_url.join(f"events/{event_id}/")
131
128
  event: dict[str, Any] = await self._get(url)
132
129
 
133
- return Event.model_validate(event)
130
+ return Event.from_dict(event)
134
131
 
135
132
  async def get_teams_information(
136
133
  self, limit: int = 100, offset: int = 0
@@ -149,7 +146,7 @@ class CTFTimeClient:
149
146
  )
150
147
  teams: list[dict[str, Any]] = response.get("results", [])
151
148
 
152
- return [Team.model_validate(team) for team in teams]
149
+ return [Team.from_dict(team) for team in teams]
153
150
 
154
151
  async def get_team_information(self, team_id: int) -> TeamComplete:
155
152
  """
@@ -161,7 +158,7 @@ class CTFTimeClient:
161
158
  url = self._base_url.join(f"teams/{team_id}/")
162
159
  team: dict[str, Any] = await self._get(url)
163
160
 
164
- return TeamComplete.model_validate(team)
161
+ return TeamComplete.from_dict(team)
165
162
 
166
163
  async def get_event_results(
167
164
  self, year: int | None = None
@@ -181,12 +178,12 @@ class CTFTimeClient:
181
178
  event: dict[str, dict] = await self._get(url)
182
179
 
183
180
  return {
184
- int(ctf_id): EventResult(**result, ctf_id=int(ctf_id))
181
+ int(ctf_id): EventResult.from_dict(result)
185
182
  for ctf_id, result in event.items()
186
183
  }
187
184
 
188
185
  async def get_votes_per_year(
189
- self, year: int | None, timeout: Timeout | int | float | None = None
186
+ self, year: int | None, timeout: Timeout | float | None = None
190
187
  ) -> list[Vote]:
191
188
  """
192
189
  Get the votes for a specific year.
@@ -206,4 +203,4 @@ class CTFTimeClient:
206
203
  url = self._base_url.join(f"votes/{year}/")
207
204
  votes: list[dict] = await self._get(url, timeout=timeout)
208
205
 
209
- return [Vote(**vote) for vote in votes]
206
+ return [Vote.from_dict(vote) for vote in votes]
@@ -0,0 +1,259 @@
1
+ from enum import StrEnum
2
+
3
+ __all__ = ["CountryCode"]
4
+
5
+
6
+ class CountryCode(StrEnum):
7
+ """
8
+ An enum for ISO 3166-1 alpha-2 country codes.
9
+ """
10
+
11
+ AFGHANISTAN = "AF"
12
+ ALAND_ISLANDS = "AX"
13
+ ALBANIA = "AL"
14
+ ALGERIA = "DZ"
15
+ AMERICAN_SAMOA = "AS"
16
+ ANDORRA = "AD"
17
+ ANGOLA = "AO"
18
+ ANGUILLA = "AI"
19
+ ANTARCTICA = "AQ"
20
+ ANTIGUA_AND_BARBUDA = "AG"
21
+ ARGENTINA = "AR"
22
+ ARMENIA = "AM"
23
+ ARUBA = "AW"
24
+ AUSTRALIA = "AU"
25
+ AUSTRIA = "AT"
26
+ AZERBAIJAN = "AZ"
27
+ BAHAMAS = "BS"
28
+ BAHRAIN = "BH"
29
+ BANGLADESH = "BD"
30
+ BARBADOS = "BB"
31
+ BELARUS = "BY"
32
+ BELGIUM = "BE"
33
+ BELIZE = "BZ"
34
+ BENIN = "BJ"
35
+ BERMUDA = "BM"
36
+ BHUTAN = "BT"
37
+ BOLIVIA_PLURINATIONAL_STATE_OF = "BO"
38
+ BONAIRE_SINT_EUSTATIUS_AND_SABA = "BQ"
39
+ BOSNIA_AND_HERZEGOVINA = "BA"
40
+ BOTSWANA = "BW"
41
+ BOUVET_ISLAND = "BV"
42
+ BRAZIL = "BR"
43
+ BRITISH_INDIAN_OCEAN_TERRITORY = "IO"
44
+ BRUNEI_DARUSSALAM = "BN"
45
+ BULGARIA = "BG"
46
+ BURKINA_FASO = "BF"
47
+ BURUNDI = "BI"
48
+ CABO_VERDE = "CV"
49
+ CAMBODIA = "KH"
50
+ CAMEROON = "CM"
51
+ CANADA = "CA"
52
+ CAYMAN_ISLANDS = "KY"
53
+ CENTRAL_AFRICAN_REPUBLIC = "CF"
54
+ CHAD = "TD"
55
+ CHILE = "CL"
56
+ CHINA = "CN"
57
+ CHRISTMAS_ISLAND = "CX"
58
+ COCOS_KEELING_ISLANDS = "CC"
59
+ COLOMBIA = "CO"
60
+ COMOROS = "KM"
61
+ CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE = "CD"
62
+ CONGO = "CG"
63
+ COOK_ISLANDS = "CK"
64
+ COSTA_RICA = "CR"
65
+ COTE_D_IVOIRE = "CI"
66
+ CROATIA = "HR"
67
+ CUBA = "CU"
68
+ CURACAO = "CW"
69
+ CYPRUS = "CY"
70
+ CZECHIA = "CZ"
71
+ DENMARK = "DK"
72
+ DJIBOUTI = "DJ"
73
+ DOMINICA = "DM"
74
+ DOMINICAN_REPUBLIC = "DO"
75
+ ECUADOR = "EC"
76
+ EGYPT = "EG"
77
+ EL_SALVADOR = "SV"
78
+ EQUATORIAL_GUINEA = "GQ"
79
+ ERITREA = "ER"
80
+ ESTONIA = "EE"
81
+ ESWATINI = "SZ"
82
+ ETHIOPIA = "ET"
83
+ FALKLAND_ISLANDS_MALVINAS = "FK"
84
+ FAROE_ISLANDS = "FO"
85
+ FIJI = "FJ"
86
+ FINLAND = "FI"
87
+ FRANCE = "FR"
88
+ FRENCH_GUIANA = "GF"
89
+ FRENCH_POLYNESIA = "PF"
90
+ FRENCH_SOUTHERN_TERRITORIES = "TF"
91
+ GABON = "GA"
92
+ GAMBIA = "GM"
93
+ GEORGIA = "GE"
94
+ GERMANY = "DE"
95
+ GHANA = "GH"
96
+ GIBRALTAR = "GI"
97
+ GREECE = "GR"
98
+ GREENLAND = "GL"
99
+ GRENADA = "GD"
100
+ GUADELOUPE = "GP"
101
+ GUAM = "GU"
102
+ GUATEMALA = "GT"
103
+ GUERNSEY = "GG"
104
+ GUINEA = "GN"
105
+ GUINEA_BISSAU = "GW"
106
+ GUYANA = "GY"
107
+ HAITI = "HT"
108
+ HEARD_ISLAND_AND_MCDONALD_ISLANDS = "HM"
109
+ HOLY_SEE = "VA"
110
+ HONDURAS = "HN"
111
+ HONG_KONG = "HK"
112
+ HUNGARY = "HU"
113
+ ICELAND = "IS"
114
+ INDIA = "IN"
115
+ INDONESIA = "ID"
116
+ IRAN_ISLAMIC_REPUBLIC_OF = "IR"
117
+ IRAQ = "IQ"
118
+ IRELAND = "IE"
119
+ ISLE_OF_MAN = "IM"
120
+ ISRAEL = "IL"
121
+ ITALY = "IT"
122
+ JAMAICA = "JM"
123
+ JAPAN = "JP"
124
+ JERSEY = "JE"
125
+ JORDAN = "JO"
126
+ KAZAKHSTAN = "KZ"
127
+ KENYA = "KE"
128
+ KIRIBATI = "KI"
129
+ KOREA_THE_DEMOCRATIC_PEOPLES_REPUBLIC_OF = "KP"
130
+ KOREA_THE_REPUBLIC_OF = "KR"
131
+ KUWAIT = "KW"
132
+ KYRGYZSTAN = "KG"
133
+ LAO_PEOPLES_DEMOCRATIC_REPUBLIC = "LA"
134
+ LATVIA = "LV"
135
+ LEBANON = "LB"
136
+ LESOTHO = "LS"
137
+ LIBERIA = "LR"
138
+ LIBYA = "LY"
139
+ LIECHTENSTEIN = "LI"
140
+ LITHUANIA = "LT"
141
+ LUXEMBOURG = "LU"
142
+ MACAO = "MO"
143
+ REPUBLIC_OF_NORTH_MACEDONIA = "MK"
144
+ MADAGASCAR = "MG"
145
+ MALAWI = "MW"
146
+ MALAYSIA = "MY"
147
+ MALDIVES = "MV"
148
+ MALI = "ML"
149
+ MALTA = "MT"
150
+ MARSHALL_ISLANDS = "MH"
151
+ MARTINIQUE = "MQ"
152
+ MAURITANIA = "MR"
153
+ MAURITIUS = "MU"
154
+ MAYOTTE = "YT"
155
+ MEXICO = "MX"
156
+ MICRONESIA_FEDERATED_STATES_OF = "FM"
157
+ MOLDOVA_THE_REPUBLIC_OF = "MD"
158
+ MONACO = "MC"
159
+ MONGOLIA = "MN"
160
+ MONTENEGRO = "ME"
161
+ MONTSERRAT = "MS"
162
+ MOROCCO = "MA"
163
+ MOZAMBIQUE = "MZ"
164
+ MYANMAR = "MM"
165
+ NAMIBIA = "NA"
166
+ NAURU = "NR"
167
+ NEPAL = "NP"
168
+ NETHERLANDS = "NL"
169
+ NEW_CALEDONIA = "NC"
170
+ NEW_ZEALAND = "NZ"
171
+ NICARAGUA = "NI"
172
+ NIGER = "NE"
173
+ NIGERIA = "NG"
174
+ NIUE = "NU"
175
+ NORFOLK_ISLAND = "NF"
176
+ NORTHERN_MARIANA_ISLANDS = "MP"
177
+ NORWAY = "NO"
178
+ OMAN = "OM"
179
+ PAKISTAN = "PK"
180
+ PALAU = "PW"
181
+ PALESTINE_STATE_OF = "PS"
182
+ PANAMA = "PA"
183
+ PAPUA_NEW_GUINEA = "PG"
184
+ PARAGUAY = "PY"
185
+ PERU = "PE"
186
+ PHILIPPINES = "PH"
187
+ PITCAIRN = "PN"
188
+ POLAND = "PL"
189
+ PORTUGAL = "PT"
190
+ PUERTO_RICO = "PR"
191
+ QATAR = "QA"
192
+ REUNION = "RE"
193
+ ROMANIA = "RO"
194
+ RUSSIAN_FEDERATION = "RU"
195
+ RWANDA = "RW"
196
+ SAINT_BARTHELEMY = "BL"
197
+ SAINT_HELENA_ASCENSION_AND_TRISTAN_DA_CUNHA = "SH"
198
+ SAINT_KITTS_AND_NEVIS = "KN"
199
+ SAINT_LUCIA = "LC"
200
+ SAINT_MARTIN_FRENCH_PART = "MF"
201
+ SAINT_PIERRE_AND_MIQUELON = "PM"
202
+ SAINT_VINCENT_AND_THE_GRENADINES = "VC"
203
+ SAMOA = "WS"
204
+ SAN_MARINO = "SM"
205
+ SAO_TOME_AND_PRINCIPE = "ST"
206
+ SAUDI_ARABIA = "SA"
207
+ SENEGAL = "SN"
208
+ SERBIA = "RS"
209
+ SEYCHELLES = "SC"
210
+ SIERRA_LEONE = "SL"
211
+ SINGAPORE = "SG"
212
+ SINT_MAARTEN_DUTCH_PART = "SX"
213
+ SLOVAKIA = "SK"
214
+ SLOVENIA = "SI"
215
+ SOLOMON_ISLANDS = "SB"
216
+ SOMALIA = "SO"
217
+ SOUTH_AFRICA = "ZA"
218
+ SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS = "GS"
219
+ SOUTH_SUDAN = "SS"
220
+ SPAIN = "ES"
221
+ SRI_LANKA = "LK"
222
+ SUDAN = "SD"
223
+ SURINAME = "SR"
224
+ SVALBARD_AND_JAN_MAYEN = "SJ"
225
+ SWEDEN = "SE"
226
+ SWITZERLAND = "CH"
227
+ SYRIAN_ARAB_REPUBLIC = "SY"
228
+ TAIWAN_PROVINCE_OF_CHINA = "TW"
229
+ TAJIKISTAN = "TJ"
230
+ TANZANIA_UNITED_REPUBLIC_OF = "TZ"
231
+ THAILAND = "TH"
232
+ TIMOR_LESTE = "TL"
233
+ TOGO = "TG"
234
+ TOKELAU = "TK"
235
+ TONGA = "TO"
236
+ TRINIDAD_AND_TOBAGO = "TT"
237
+ TUNISIA = "TN"
238
+ TURKEY = "TR"
239
+ TURKMENISTAN = "TM"
240
+ TURKS_AND_CAICOS_ISLANDS = "TC"
241
+ TUVALU = "TV"
242
+ UGANDA = "UG"
243
+ UKRAINE = "UA"
244
+ UNITED_ARAB_EMIRATES = "AE"
245
+ UNITED_KINGDOM_OF_GREAT_BRITAIN_AND_NORTHERN_IRELAND = "GB"
246
+ UNITED_STATES_MINOR_OUTLYING_ISLANDS = "UM"
247
+ UNITED_STATES_OF_AMERICA = "US"
248
+ URUGUAY = "UY"
249
+ UZBEKISTAN = "UZ"
250
+ VANUATU = "VU"
251
+ VENEZUELA_BOLIVARIAN_REPUBLIC_OF = "VE"
252
+ VIET_NAM = "VN"
253
+ VIRGIN_ISLANDS_BRITISH = "VG"
254
+ VIRGIN_ISLANDS_US = "VI"
255
+ WALLIS_AND_FUTUNA = "WF"
256
+ WESTERN_SAHARA = "EH"
257
+ YEMEN = "YE"
258
+ ZAMBIA = "ZM"
259
+ ZIMBABWE = "ZW"
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+
3
+ from dataclasses_json import DataClassJsonMixin, Undefined, dataclass_json
4
+
5
+ __all__ = ["Duration"]
6
+
7
+
8
+ @dataclass_json(undefined=Undefined.EXCLUDE)
9
+ @dataclass(frozen=True)
10
+ class Duration(DataClassJsonMixin):
11
+ hours: int
12
+ days: int
@@ -0,0 +1,53 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+
4
+ from dataclasses_json import DataClassJsonMixin, Undefined, dataclass_json
5
+ from yarl import URL
6
+
7
+ from .duration import Duration
8
+ from .team import BaseTeam, TeamResult
9
+
10
+ __all__ = ["Event", "EventResult"]
11
+
12
+
13
+ @dataclass_json(undefined=Undefined.EXCLUDE)
14
+ @dataclass(frozen=True)
15
+ class Event(DataClassJsonMixin):
16
+ """Represents a CTF event."""
17
+
18
+ organizers: list[BaseTeam]
19
+ ctftime_url: URL
20
+ ctf_id: int
21
+ weight: float
22
+ duration: Duration
23
+ live_feed: str
24
+ logo: URL | str
25
+ id: int
26
+ title: str
27
+ participants: int
28
+ location: str
29
+ description: str
30
+ format: str
31
+ is_votable_now: bool
32
+ prizes: str
33
+ restrictions: str
34
+ url: URL | str
35
+ public_votable: bool
36
+ start: datetime | str
37
+ finish: datetime | str
38
+
39
+ def __post_init__(self):
40
+ if isinstance(self.start, str):
41
+ object.__setattr__(self, "start", datetime.fromisoformat(self.start))
42
+ if isinstance(self.finish, str):
43
+ object.__setattr__(self, "finish", datetime.fromisoformat(self.finish))
44
+
45
+
46
+ @dataclass_json(undefined=Undefined.EXCLUDE)
47
+ @dataclass(frozen=True)
48
+ class EventResult(DataClassJsonMixin):
49
+ """Represents a CTF event result."""
50
+
51
+ title: str
52
+ time: datetime
53
+ scores: list[TeamResult]
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+
3
+ from dataclasses_json import DataClassJsonMixin, Undefined, dataclass_json
4
+
5
+ __all__ = ["Rating"]
6
+
7
+
8
+ @dataclass_json(undefined=Undefined.EXCLUDE)
9
+ @dataclass(frozen=True)
10
+ class Rating(DataClassJsonMixin):
11
+ """Represents a CTF team rating."""
12
+
13
+ rating_place: int | None = None
14
+ organizer_points: float | None = None
15
+ rating_points: float | None = None
16
+ country_place: int | None = None
@@ -0,0 +1,61 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from dataclasses_json import DataClassJsonMixin, Undefined, config, dataclass_json
4
+ from yarl import URL
5
+
6
+ from .country import CountryCode
7
+ from .rating import Rating
8
+
9
+ __all__ = ["BaseTeam", "Team", "TeamComplete", "TeamRank", "TeamResult"]
10
+
11
+
12
+ @dataclass_json(undefined=Undefined.EXCLUDE)
13
+ @dataclass(frozen=True)
14
+ class BaseTeam(DataClassJsonMixin):
15
+ """Represents a CTF team. Contains only the minimal information."""
16
+
17
+ team_id: int = field(metadata=config(field_name="id"))
18
+ team_name: str = field(metadata=config(field_name="name"))
19
+
20
+
21
+ @dataclass_json(undefined=Undefined.EXCLUDE)
22
+ @dataclass(frozen=True)
23
+ class Team(BaseTeam):
24
+ """Represents a CTF team"""
25
+
26
+ team_country: CountryCode | None = None
27
+ academic: bool = False
28
+ aliases: list[str] = field(default_factory=list)
29
+
30
+
31
+ @dataclass_json(undefined=Undefined.EXCLUDE)
32
+ @dataclass(frozen=True)
33
+ class TeamRank(BaseTeam):
34
+ """Represents a CTF team in the leaderboard"""
35
+
36
+ points: float
37
+ country_place: int | None = None
38
+ place: int | None = None
39
+ events: int | None = None
40
+
41
+
42
+ @dataclass_json(undefined=Undefined.EXCLUDE)
43
+ @dataclass(frozen=True)
44
+ class TeamResult(DataClassJsonMixin):
45
+ """Represents a CTF team result"""
46
+
47
+ team_id: int
48
+ points: float
49
+ place: int
50
+
51
+
52
+ @dataclass_json(undefined=Undefined.EXCLUDE)
53
+ @dataclass(frozen=True)
54
+ class TeamComplete(Team):
55
+ """Represents a CTF team with complete information"""
56
+
57
+ primary_alias: str | None = None
58
+ logo: URL | str | None = None
59
+ university: str | None = None
60
+ university_website: URL | str | None = None
61
+ rating: dict[int, Rating | None] = field(default_factory=dict)
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+
4
+ from dataclasses_json import DataClassJsonMixin, Undefined, dataclass_json
5
+
6
+ __all__ = ["Vote"]
7
+
8
+
9
+ @dataclass_json(undefined=Undefined.EXCLUDE)
10
+ @dataclass(frozen=True)
11
+ class Vote(DataClassJsonMixin):
12
+ event_id: int
13
+ user_id: int
14
+ user_teams: list[int] # list of team IDs
15
+ weight: str
16
+ creation_date: datetime