Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions ctftime_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

import httpx
from httpx import URL, Timeout
from pydantic_extra_types.country import CountryAlpha2

from ctftime_api.models.country import CountryCode
from ctftime_api.models.event import Event, EventResult
from ctftime_api.models.team import TeamRank, Team, TeamComplete
from ctftime_api.models.team import Team, TeamComplete, TeamRank
from ctftime_api.models.vote import Vote

__all__ = ["CTFTimeClient"]
Expand Down Expand Up @@ -65,10 +65,10 @@ async def get_top_teams_per_year(
response: dict[str, list[dict]] = await self._get(url, params={"limit": limit})
teams = response.get(f"{year}", [])

return [TeamRank.model_validate(team) for team in teams]
return [TeamRank.from_dict(team) for team in teams]

async def get_top_team_by_country(
self, country: str | CountryAlpha2
self, country: str | CountryCode
) -> list[TeamRank]:
"""
Get the top teams in the leaderboard for a specific country.
Expand All @@ -78,9 +78,7 @@ async def get_top_team_by_country(
:raise httpx.HTTPStatusError: If the response status code is not successful.
:raise ValueError: If the country is not a two-letter country code or a pycountry Country object.
"""
if isinstance(country, CountryAlpha2):
country = country
elif isinstance(country, str):
if isinstance(country, str):
if len(country) != 2:
raise ValueError(
"Country must be a two-letter country code or a pycountry Country object."
Expand All @@ -89,7 +87,7 @@ async def get_top_team_by_country(
url = self._base_url.join("top-by-country/").join(f"{country}/")
teams: list[dict[str, Any]] = await self._get(url)

return [TeamRank.model_validate(team) for team in teams]
return [TeamRank.from_dict(team) for team in teams]

async def get_events_information(
self, start: int | datetime, end: int | datetime, limit: int = 10
Expand Down Expand Up @@ -118,7 +116,7 @@ async def get_events_information(
url, params={"start": start, "finish": end, "limit": limit}
)

return [Event.model_validate(event) for event in events]
return [Event.from_dict(event) for event in events]

async def get_event_information(self, event_id: int) -> Event:
"""
Expand All @@ -130,7 +128,7 @@ async def get_event_information(self, event_id: int) -> Event:
url = self._base_url.join(f"events/{event_id}/")
event: dict[str, Any] = await self._get(url)

return Event.model_validate(event)
return Event.from_dict(event)

async def get_teams_information(
self, limit: int = 100, offset: int = 0
Expand All @@ -149,7 +147,7 @@ async def get_teams_information(
)
teams: list[dict[str, Any]] = response.get("results", [])

return [Team.model_validate(team) for team in teams]
return [Team.from_dict(team) for team in teams]

async def get_team_information(self, team_id: int) -> TeamComplete:
"""
Expand All @@ -161,7 +159,7 @@ async def get_team_information(self, team_id: int) -> TeamComplete:
url = self._base_url.join(f"teams/{team_id}/")
team: dict[str, Any] = await self._get(url)

return TeamComplete.model_validate(team)
return TeamComplete.from_dict(team)

async def get_event_results(
self, year: int | None = None
Expand All @@ -181,7 +179,7 @@ async def get_event_results(
event: dict[str, dict] = await self._get(url)

return {
int(ctf_id): EventResult(**result, ctf_id=int(ctf_id))
int(ctf_id): EventResult.from_dict(result)
for ctf_id, result in event.items()
}

Expand All @@ -206,4 +204,4 @@ async def get_votes_per_year(
url = self._base_url.join(f"votes/{year}/")
votes: list[dict] = await self._get(url, timeout=timeout)

return [Vote(**vote) for vote in votes]
return [Vote.from_dict(vote) for vote in votes]
259 changes: 259 additions & 0 deletions ctftime_api/models/country.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
from enum import StrEnum

__all__ = ["CountryCode"]


class CountryCode(StrEnum):
"""
An enum for ISO 3166-1 alpha-2 country codes.
"""

AFGHANISTAN = "AF"
ALAND_ISLANDS = "AX"
ALBANIA = "AL"
ALGERIA = "DZ"
AMERICAN_SAMOA = "AS"
ANDORRA = "AD"
ANGOLA = "AO"
ANGUILLA = "AI"
ANTARCTICA = "AQ"
ANTIGUA_AND_BARBUDA = "AG"
ARGENTINA = "AR"
ARMENIA = "AM"
ARUBA = "AW"
AUSTRALIA = "AU"
AUSTRIA = "AT"
AZERBAIJAN = "AZ"
BAHAMAS = "BS"
BAHRAIN = "BH"
BANGLADESH = "BD"
BARBADOS = "BB"
BELARUS = "BY"
BELGIUM = "BE"
BELIZE = "BZ"
BENIN = "BJ"
BERMUDA = "BM"
BHUTAN = "BT"
BOLIVIA_PLURINATIONAL_STATE_OF = "BO"
BONAIRE_SINT_EUSTATIUS_AND_SABA = "BQ"
BOSNIA_AND_HERZEGOVINA = "BA"
BOTSWANA = "BW"
BOUVET_ISLAND = "BV"
BRAZIL = "BR"
BRITISH_INDIAN_OCEAN_TERRITORY = "IO"
BRUNEI_DARUSSALAM = "BN"
BULGARIA = "BG"
BURKINA_FASO = "BF"
BURUNDI = "BI"
CABO_VERDE = "CV"
CAMBODIA = "KH"
CAMEROON = "CM"
CANADA = "CA"
CAYMAN_ISLANDS = "KY"
CENTRAL_AFRICAN_REPUBLIC = "CF"
CHAD = "TD"
CHILE = "CL"
CHINA = "CN"
CHRISTMAS_ISLAND = "CX"
COCOS_KEELING_ISLANDS = "CC"
COLOMBIA = "CO"
COMOROS = "KM"
CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE = "CD"
CONGO = "CG"
COOK_ISLANDS = "CK"
COSTA_RICA = "CR"
COTE_D_IVOIRE = "CI"
CROATIA = "HR"
CUBA = "CU"
CURACAO = "CW"
CYPRUS = "CY"
CZECHIA = "CZ"
DENMARK = "DK"
DJIBOUTI = "DJ"
DOMINICA = "DM"
DOMINICAN_REPUBLIC = "DO"
ECUADOR = "EC"
EGYPT = "EG"
EL_SALVADOR = "SV"
EQUATORIAL_GUINEA = "GQ"
ERITREA = "ER"
ESTONIA = "EE"
ESWATINI = "SZ"
ETHIOPIA = "ET"
FALKLAND_ISLANDS_MALVINAS = "FK"
FAROE_ISLANDS = "FO"
FIJI = "FJ"
FINLAND = "FI"
FRANCE = "FR"
FRENCH_GUIANA = "GF"
FRENCH_POLYNESIA = "PF"
FRENCH_SOUTHERN_TERRITORIES = "TF"
GABON = "GA"
GAMBIA = "GM"
GEORGIA = "GE"
GERMANY = "DE"
GHANA = "GH"
GIBRALTAR = "GI"
GREECE = "GR"
GREENLAND = "GL"
GRENADA = "GD"
GUADELOUPE = "GP"
GUAM = "GU"
GUATEMALA = "GT"
GUERNSEY = "GG"
GUINEA = "GN"
GUINEA_BISSAU = "GW"
GUYANA = "GY"
HAITI = "HT"
HEARD_ISLAND_AND_MCDONALD_ISLANDS = "HM"
HOLY_SEE = "VA"
HONDURAS = "HN"
HONG_KONG = "HK"
HUNGARY = "HU"
ICELAND = "IS"
INDIA = "IN"
INDONESIA = "ID"
IRAN_ISLAMIC_REPUBLIC_OF = "IR"
IRAQ = "IQ"
IRELAND = "IE"
ISLE_OF_MAN = "IM"
ISRAEL = "IL"
ITALY = "IT"
JAMAICA = "JM"
JAPAN = "JP"
JERSEY = "JE"
JORDAN = "JO"
KAZAKHSTAN = "KZ"
KENYA = "KE"
KIRIBATI = "KI"
KOREA_THE_DEMOCRATIC_PEOPLES_REPUBLIC_OF = "KP"
KOREA_THE_REPUBLIC_OF = "KR"
KUWAIT = "KW"
KYRGYZSTAN = "KG"
LAO_PEOPLES_DEMOCRATIC_REPUBLIC = "LA"
LATVIA = "LV"
LEBANON = "LB"
LESOTHO = "LS"
LIBERIA = "LR"
LIBYA = "LY"
LIECHTENSTEIN = "LI"
LITHUANIA = "LT"
LUXEMBOURG = "LU"
MACAO = "MO"
REPUBLIC_OF_NORTH_MACEDONIA = "MK"
MADAGASCAR = "MG"
MALAWI = "MW"
MALAYSIA = "MY"
MALDIVES = "MV"
MALI = "ML"
MALTA = "MT"
MARSHALL_ISLANDS = "MH"
MARTINIQUE = "MQ"
MAURITANIA = "MR"
MAURITIUS = "MU"
MAYOTTE = "YT"
MEXICO = "MX"
MICRONESIA_FEDERATED_STATES_OF = "FM"
MOLDOVA_THE_REPUBLIC_OF = "MD"
MONACO = "MC"
MONGOLIA = "MN"
MONTENEGRO = "ME"
MONTSERRAT = "MS"
MOROCCO = "MA"
MOZAMBIQUE = "MZ"
MYANMAR = "MM"
NAMIBIA = "NA"
NAURU = "NR"
NEPAL = "NP"
NETHERLANDS = "NL"
NEW_CALEDONIA = "NC"
NEW_ZEALAND = "NZ"
NICARAGUA = "NI"
NIGER = "NE"
NIGERIA = "NG"
NIUE = "NU"
NORFOLK_ISLAND = "NF"
NORTHERN_MARIANA_ISLANDS = "MP"
NORWAY = "NO"
OMAN = "OM"
PAKISTAN = "PK"
PALAU = "PW"
PALESTINE_STATE_OF = "PS"
PANAMA = "PA"
PAPUA_NEW_GUINEA = "PG"
PARAGUAY = "PY"
PERU = "PE"
PHILIPPINES = "PH"
PITCAIRN = "PN"
POLAND = "PL"
PORTUGAL = "PT"
PUERTO_RICO = "PR"
QATAR = "QA"
REUNION = "RE"
ROMANIA = "RO"
RUSSIAN_FEDERATION = "RU"
RWANDA = "RW"
SAINT_BARTHELEMY = "BL"
SAINT_HELENA_ASCENSION_AND_TRISTAN_DA_CUNHA = "SH"
SAINT_KITTS_AND_NEVIS = "KN"
SAINT_LUCIA = "LC"
SAINT_MARTIN_FRENCH_PART = "MF"
SAINT_PIERRE_AND_MIQUELON = "PM"
SAINT_VINCENT_AND_THE_GRENADINES = "VC"
SAMOA = "WS"
SAN_MARINO = "SM"
SAO_TOME_AND_PRINCIPE = "ST"
SAUDI_ARABIA = "SA"
SENEGAL = "SN"
SERBIA = "RS"
SEYCHELLES = "SC"
SIERRA_LEONE = "SL"
SINGAPORE = "SG"
SINT_MAARTEN_DUTCH_PART = "SX"
SLOVAKIA = "SK"
SLOVENIA = "SI"
SOLOMON_ISLANDS = "SB"
SOMALIA = "SO"
SOUTH_AFRICA = "ZA"
SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS = "GS"
SOUTH_SUDAN = "SS"
SPAIN = "ES"
SRI_LANKA = "LK"
SUDAN = "SD"
SURINAME = "SR"
SVALBARD_AND_JAN_MAYEN = "SJ"
SWEDEN = "SE"
SWITZERLAND = "CH"
SYRIAN_ARAB_REPUBLIC = "SY"
TAIWAN_PROVINCE_OF_CHINA = "TW"
TAJIKISTAN = "TJ"
TANZANIA_UNITED_REPUBLIC_OF = "TZ"
THAILAND = "TH"
TIMOR_LESTE = "TL"
TOGO = "TG"
TOKELAU = "TK"
TONGA = "TO"
TRINIDAD_AND_TOBAGO = "TT"
TUNISIA = "TN"
TURKEY = "TR"
TURKMENISTAN = "TM"
TURKS_AND_CAICOS_ISLANDS = "TC"
TUVALU = "TV"
UGANDA = "UG"
UKRAINE = "UA"
UNITED_ARAB_EMIRATES = "AE"
UNITED_KINGDOM_OF_GREAT_BRITAIN_AND_NORTHERN_IRELAND = "GB"
UNITED_STATES_MINOR_OUTLYING_ISLANDS = "UM"
UNITED_STATES_OF_AMERICA = "US"
URUGUAY = "UY"
UZBEKISTAN = "UZ"
VANUATU = "VU"
VENEZUELA_BOLIVARIAN_REPUBLIC_OF = "VE"
VIET_NAM = "VN"
VIRGIN_ISLANDS_BRITISH = "VG"
VIRGIN_ISLANDS_US = "VI"
WALLIS_AND_FUTUNA = "WF"
WESTERN_SAHARA = "EH"
YEMEN = "YE"
ZAMBIA = "ZM"
ZIMBABWE = "ZW"
8 changes: 6 additions & 2 deletions ctftime_api/models/duration.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from pydantic import BaseModel
from dataclasses import dataclass

from dataclasses_json import DataClassJsonMixin, Undefined, dataclass_json

__all__ = ["Duration"]


class Duration(BaseModel):
@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass(frozen=True)
class Duration(DataClassJsonMixin):
hours: int
days: int
Loading