From 9db2dbb2bc930df3561d6f7c5503d71026d9f6d5 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Tue, 13 Aug 2024 22:58:27 +0100 Subject: [PATCH 01/17] Models to support organization and Standing endpoints --- pyproject.toml | 2 +- sendou/client.py | 16 +++- sendou/models/__init__.py | 2 +- sendou/models/organization.py | 90 ++++++++++++++++++++ sendou/models/tournament/bracket/Standing.py | 71 +++++++++++++++ sendou/models/tournament/bracket/__init__.py | 1 + sendou/models/tournament/bracket/bracket.py | 2 +- sendou/models/tournament/tournament.py | 24 +++++- 8 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 sendou/models/organization.py create mode 100644 sendou/models/tournament/bracket/Standing.py diff --git a/pyproject.toml b/pyproject.toml index f673693..95d7cfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sendou-py" -version = "1.1.0" +version = "1.2.0" description = "An async Python library for Sendou.ink" authors = ["Vincent Lee "] license = "MIT" diff --git a/sendou/client.py b/sendou/client.py index 00e5d7a..b73da1e 100644 --- a/sendou/client.py +++ b/sendou/client.py @@ -1,7 +1,7 @@ from typing import Dict, List, Optional, Union from datetime import datetime, timedelta from aiohttp_client_cache import CacheBackend -from sendou.models import User, Tournament, Match, CalendarEntry +from sendou.models import User, Tournament, Match, CalendarEntry, Organization from sendou.requests import RequestsClient @@ -118,3 +118,17 @@ async def get_tournament_matches(self, match_id: str) -> Optional[Match]: path = Match.api_route(match_id=match_id) data = await self.__client.get_response(path) return Match(data, self.__client) + + async def get_organization(self, organization_id: str) -> Optional[Organization]: + """ + Get Sendou.ink organization + + Attributes: + organization_id: Organization ID + + Returns: + (Optional[Organization]): Organization (None if not found) + """ + path = Organization.api_route(organization_id=organization_id) + data = await self.__client.get_response(path) + return Organization(data, self.__client) diff --git a/sendou/models/__init__.py b/sendou/models/__init__.py index 67a8e86..843cad6 100644 --- a/sendou/models/__init__.py +++ b/sendou/models/__init__.py @@ -8,4 +8,4 @@ from .plusServer import PlusTiers from .stageMapList import Stage, ModeShort, StageWithMode from .calendarEntry import CalendarEntry - \ No newline at end of file +from .organization import Organization diff --git a/sendou/models/organization.py b/sendou/models/organization.py new file mode 100644 index 0000000..70b46e6 --- /dev/null +++ b/sendou/models/organization.py @@ -0,0 +1,90 @@ +""" +Org Model +""" +from sendou.models.baseModel import BaseModel +from sendou.requests import RequestsClient + +from typing import Optional, List +from enum import Enum + +from .user import User + + +class OrganizationRole(Enum): + """ + Represents a role of member in the organization + """ + ADMIN = "admin" + MEMBER = "member" + ORGANIZER = "organizer" + STREAMER = "streamer" + + +class OrganizationMember(BaseModel): + """ + Represents a member of the organization + + Attributes: + user_id: User ID + name: User name + discord_id: Discord ID + role: OrganizationRole + role_display_name: Display name of the role + """ + user_id: int + name: str + discord_id: str + role: OrganizationRole + role_display_name: Optional[str] + + def __init__(self, data: dict, request_client: RequestsClient): + super().__init__(data, request_client) + self.user_id = data.get("userId") + self.name = data.get("name") + self.discord_id = data.get("discordId") + self.role = OrganizationRole(data.get("role")) + self.role_display_name = data.get("roleDisplayName", None) + + async def get_user(self) -> Optional[User]: + """ + Get the user object for this member + + Returns: + (User): User object + """ + path = User.api_route(user_id=self.user_id) + data = await self._request_client.get_response(path) + return User(data, self._request_client) + + +class Organization(BaseModel): + """ + Represents an organization + + Attributes: + id: Organization ID + name: Organization name + description: Organization description + url: Organization URL + logo_url: Organization logo URL + social_link_urls: List of social link URLs + members: List of OrganizationMember + """ + id: int + name: str + description: Optional[str] + url: str + logo_url: Optional[str] + social_link_urls: List[str] + members: List[OrganizationMember] + + def __init__(self, data: dict, request_client: RequestsClient): + super().__init__(data, request_client) + self.id = data.get("id") + self.name = data.get("name") + self.description = data.get("description", None) + self.url = data.get("url") + self.logo_url = data.get("logoUrl", None) + self.social_link_urls = data.get("socialLinkUrls") + self.members = [OrganizationMember(member, request_client) for member in data.get("members")] + diff --git a/sendou/models/tournament/bracket/Standing.py b/sendou/models/tournament/bracket/Standing.py new file mode 100644 index 0000000..a656bfe --- /dev/null +++ b/sendou/models/tournament/bracket/Standing.py @@ -0,0 +1,71 @@ +""" +Standings Model +""" +from typing import Optional + + +class StandingStats: + """ + Stats for a Standing + + Attributes: + set_wins (int): Set Wins + set_loses (int): Set Loses + map_wins (int): Map Wins + map_loses (int): Map Loses + points (int): Points + wins_against_tied (int): Wins Against Tied + buchholz_sets (Optional[int]): Buchholz Sets + buchholz_maps (Optional[int]): Buchholz Maps + """ + set_wins: int + set_loses: int + map_wins: int + map_loses: int + points: int + wins_against_tied: int + buchholz_sets: Optional[int] + buchholz_maps: Optional[int] + + def __init__(self, data: dict): + self.set_wins = data.get("setWins", 0) + self.set_loses = data.get("setLoses", 0) + self.map_wins = data.get("mapWins", 0) + self.map_loses = data.get("mapLoses", 0) + self.points = data.get("points", 0) + self.wins_against_tied = data.get("winsAgainstTied", 0) + self.buchholz_sets = data.get("buchholzSets", None) + self.buchholz_maps = data.get("buchholzMaps", None) + + +class BracketStanding: + """ + Represents a Team's standing in a bracket + + Attributes: + tournament_team_id (int): Tournament Team ID + placement (int): Placement + stats (StandingStats): Standing Stats + """ + tournament_team_id: int + placement: int + stats: StandingStats + + def __init__(self, data: dict): + self.tournament_team_id = data.get("tournamentTeamId", 0) + self.placement = data.get("placement", 0) + self.stats = StandingStats(data.get("stats", {})) + + @staticmethod + def api_route(**kwargs) -> str: + """ + Returns API route for the model + + Args: + tournament_id (str): Tournament ID + bracket_index (int): Bracket Index + + Returns: + str: API Route + """ + return f"api/tournament/{kwargs.get('tournament_id')}/brackets/{kwargs.get('bracket_index')}/standings" diff --git a/sendou/models/tournament/bracket/__init__.py b/sendou/models/tournament/bracket/__init__.py index 9704f6b..8646934 100644 --- a/sendou/models/tournament/bracket/__init__.py +++ b/sendou/models/tournament/bracket/__init__.py @@ -1,2 +1,3 @@ from .bracket import Bracket from .type import BracketType, RoundType, MatchResult +from .Standing import BracketStanding diff --git a/sendou/models/tournament/bracket/bracket.py b/sendou/models/tournament/bracket/bracket.py index bdf3188..10d5485 100644 --- a/sendou/models/tournament/bracket/bracket.py +++ b/sendou/models/tournament/bracket/bracket.py @@ -234,7 +234,7 @@ class Bracket(BaseModel): Sendou.ink Tournament Bracket Info Attributes: - data (Any): Bracket Data + data (BracketData): Bracket Data meta (BracketMeta): Bracket Metadata """ data: BracketData # https://github.com/Sendouc/sendou.ink/blob/rewrite/app/features/api-public/schema.ts#L232 diff --git a/sendou/models/tournament/tournament.py b/sendou/models/tournament/tournament.py index 34f4747..b6e2da9 100644 --- a/sendou/models/tournament/tournament.py +++ b/sendou/models/tournament/tournament.py @@ -1,14 +1,16 @@ """ Tournament Info Model """ +from datetime import datetime +from typing import Any, Dict, List, Optional + +from dateutil import parser + from sendou.models.baseModel import BaseModel from sendou.requests import RequestsClient +from .bracket import Bracket, BracketType, BracketStanding from .team import TournamentTeam -from .bracket import Bracket, BracketType -from datetime import datetime -from dateutil import parser -from typing import Any, Dict, List, Optional class TournamentTeamInfo: """ @@ -66,6 +68,17 @@ async def get_bracket_data(self) -> Bracket: data = await self._request_client.get_response(path) return Bracket(data, self._request_client) + async def get_standings(self) -> List[BracketStanding]: + """ + Get the bracket standings + + Returns: + (List[BracketStanding]): List of Bracket Standings + """ + path = Bracket.api_route(tournament_id=self.__tournament_id, bracket_index=self._index) + data = await self._request_client.get_response(path) + return [BracketStanding(standing) for standing in data] + class Tournament(BaseModel): """ @@ -79,6 +92,7 @@ class Tournament(BaseModel): start_time (datetime): Start Time teams (TournamentTeamInfo): Tournament Team Info brackets (List[TournamentBracket]): Tournament Brackets + organization_id (Optional[int]): Organization ID """ id: int name: str @@ -87,6 +101,7 @@ class Tournament(BaseModel): start_time: datetime teams: TournamentTeamInfo brackets: List[TournamentBracket] + organization_id: Optional[int] def __init__(self, id: int, data: dict, request_client: RequestsClient): """ @@ -106,6 +121,7 @@ def __init__(self, id: int, data: dict, request_client: RequestsClient): self.teams = TournamentTeamInfo(data.get("teams", {})) self.brackets = [TournamentBracket(bracket, index, self.id, request_client) for index, bracket in enumerate(data.get("brackets", []))] + self.organization_id = data.get("organizationId", None) async def get_teams(self) -> List[TournamentTeam]: """ From 6a99bbc639a34fa2a18a4e4f79d93044c1ef0168 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 22:52:11 +0100 Subject: [PATCH 02/17] Add unit tests & help scripts --- .github/workflows/publish-to-pypi.yml | 4 +- .github/workflows/pull_request.yml | 39 +++ poetry.lock | 308 ++++++++++++++++------- pyproject.toml | 25 +- scripts/checkUpstreamSchema.py | 35 +++ scripts/updateUpstreamSchema.py | 36 +++ sendou/client.py | 2 +- sendou/models/organization.py | 65 +++-- sendou/models/tournament/tournament.py | 14 ++ tests/sendou/models/test_organization.py | 56 +++++ 10 files changed, 467 insertions(+), 117 deletions(-) create mode 100644 .github/workflows/pull_request.yml create mode 100644 scripts/checkUpstreamSchema.py create mode 100644 scripts/updateUpstreamSchema.py create mode 100644 tests/sendou/models/test_organization.py diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 1b6cfd1..d2fb31e 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,7 +26,9 @@ jobs: virtualenvs-in-project: true installer-parallel: true - name: Install dependencies - run: poetry install --no-interaction --no-root + run: poetry install --no-interaction --no-root --with=dev + - name: Check Schema Hash with upstream + run: python scripts/checkUpstreamSchema.py - name: Mint token id: mint uses: tschm/token-mint-action@v1.0.2 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..3b98991 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,39 @@ +name: Pull request + +# Make sure only the latest push to the PR's source branch runs and cancel any on-going previous run +concurrency: + group: ${{ github.head_ref }} + cancel-in-progress: true + +on: + pull_request: + +jobs: + Pull-Request: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up python + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + - name: Install dependencies + run: poetry install --no-interaction --no-root --with=dev + - name: Check Schema Hash with upstream + run: python scripts/checkUpstreamSchema.py + - name: Run pytest + uses: pavelzw/pytest-action@v2 + with: + verbose: false + emoji: false + job-summary: true + click-to-expand: true + report-title: 'Test Report' diff --git a/poetry.lock b/poetry.lock index 948e858..4e6be04 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,91 +1,103 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.3.5" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, + {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, +] + [[package]] name = "aiohttp" -version = "3.9.5" +version = "3.10.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, + {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, + {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, + {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, + {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, + {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, + {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, + {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, + {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, + {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, + {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, + {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, + {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, + {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, + {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, + {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, + {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, + {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, + {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, + {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, + {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, + {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, + {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, + {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, + {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, + {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, ] [package.dependencies] +aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" @@ -94,17 +106,17 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiohttp-client-cache" -version = "0.11.0" +version = "0.11.1" description = "Persistent cache for aiohttp requests" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "aiohttp_client_cache-0.11.0-py3-none-any.whl", hash = "sha256:5b6217bc26a7b3f5f939809b63a66b67658b660809cd38869d7d45066a26d079"}, - {file = "aiohttp_client_cache-0.11.0.tar.gz", hash = "sha256:0766fff4eda05498c7525374a587810dcc2ccb7b256809dde52ae8790a8453eb"}, + {file = "aiohttp_client_cache-0.11.1-py3-none-any.whl", hash = "sha256:06ea196e35219a6f1ecc2f96639106eeea5fc1ec9808c805aa3a2e5cbfa62df6"}, + {file = "aiohttp_client_cache-0.11.1.tar.gz", hash = "sha256:32e63ad210240f8224f3e12772fe53ac102cf24c7cf18ddb86acbb9fdf9e4b6f"}, ] [package.dependencies] @@ -114,13 +126,13 @@ itsdangerous = ">=2.0" url-normalize = ">=1.4,<2.0" [package.extras] -all = ["aioboto3 (>=9.0)", "aiobotocore (>=2.0)", "aiofiles (>=0.6.0)", "aiosqlite (>=0.16)", "motor (>=3.1)", "redis (>=4.2)"] +all = ["aioboto3 (>=9.0)", "aiobotocore (>=2.0)", "aiofiles (>=0.6.0)", "aiosqlite (>=0.20)", "motor (>=3.1)", "redis (>=4.2)"] docs = ["furo (>=2023.8,<2024.0)", "linkify-it-py (>=2.0)", "markdown-it-py (>=2.2)", "myst-parser (>=2.0)", "python-forge (>=18.6,<19.0)", "sphinx (==7.1.2)", "sphinx-autodoc-typehints (>=1.23,<2.0)", "sphinx-automodapi (>=0.15)", "sphinx-copybutton (>=0.3,<0.4)", "sphinx-inline-tabs (>=2023.4)", "sphinxcontrib-apidoc (>=0.3)"] dynamodb = ["aioboto3 (>=9.0)", "aiobotocore (>=2.0)"] -filesystem = ["aiofiles (>=0.6.0)", "aiosqlite (>=0.16)"] +filesystem = ["aiofiles (>=0.6.0)", "aiosqlite (>=0.20)"] mongodb = ["motor (>=3.1)"] redis = ["redis (>=4.2)"] -sqlite = ["aiosqlite (>=0.16)"] +sqlite = ["aiosqlite (>=0.20)"] [[package]] name = "aiosignal" @@ -162,22 +174,47 @@ files = [ [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +test = ["pytest (>=6)"] [[package]] name = "frozenlist" @@ -276,6 +313,17 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -386,6 +434,54 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -411,6 +507,28 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "url-normalize" version = "1.4.3" @@ -531,4 +649,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ab3e99024aa3b72234f4f62cac79bfa5b70f61248013db56f2f0edaccf1136a2" +content-hash = "b85360b0e8bbaf8bd2665a05633f6eb637a5fe385ad20e331cfac3c9293d2880" diff --git a/pyproject.toml b/pyproject.toml index 95d7cfa..dc83f93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,14 @@ +[build-system] +requires = [ "poetry-core",] +build-backend = "poetry.core.masonry.api" + [tool.poetry] name = "sendou-py" version = "1.2.0" description = "An async Python library for Sendou.ink" -authors = ["Vincent Lee "] +authors = [ "Vincent Lee ",] license = "MIT" readme = "README.md" -packages = [{include = "sendou"}] homepage = "https://github.com/IPLSplatoon/sendou.py/" repository = "https://github.com/IPLSplatoon/sendou.py/" documentation = "https://sendou.opensource.iplabs.work/" @@ -15,14 +18,24 @@ classifiers = [ "Development Status :: 4 - Beta", "Framework :: AsyncIO" ] +packages = [{include="sendou"}] [tool.poetry.dependencies] python = "^3.10" -asyncio = "^3.4.3" python-dateutil = "^2.8.2" aiohttp-client-cache = "^0.11.0" +[tool.sendou-py.source] +schema_commit = "5ad69f941f3844d89153e4ac03184a0482e8df3b" +schema_path = "app/features/api-public/schema.ts" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib", +] +testpaths = ["tests"] + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.2" +asyncio = "^3.4.3" +toml = "^0.10.2" diff --git a/scripts/checkUpstreamSchema.py b/scripts/checkUpstreamSchema.py new file mode 100644 index 0000000..368adb1 --- /dev/null +++ b/scripts/checkUpstreamSchema.py @@ -0,0 +1,35 @@ +""" +Check Upstream Schema + +This script checks the version store in pyproject.toml with the latest schema SHA in the sendou.ink Github repo +and raises an error if there is a mismatch. +""" + +import tomllib +import asyncio +import aiohttp + + +async def main(): + # Open TOML and get data + with open("../pyproject.toml", "rb") as f: + data = tomllib.load(f) + schema_commit = data["tool"]["sendou-py"]["source"]["schema_commit"] + schema_path = data["tool"]["sendou-py"]["source"]["schema_path"] + + async with aiohttp.ClientSession() as session: + async with session.get(f"https://api.github.com/repos/sendouc/sendou.ink/commits?path={schema_path}&per_page=1", headers={ + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }) as response: + if response.status != 200: + raise Exception("Failed to get response from Github API") + data = await response.json() + latest_sha = data[0]["sha"] + if schema_commit != latest_sha: + print(f"Schema is not up to date. Project SHA: {schema_commit}, Latest SHA: {latest_sha}") + exit(1) + print("Schema is up to date ✅") + exit(0) + +asyncio.run(main()) diff --git a/scripts/updateUpstreamSchema.py b/scripts/updateUpstreamSchema.py new file mode 100644 index 0000000..5a88c49 --- /dev/null +++ b/scripts/updateUpstreamSchema.py @@ -0,0 +1,36 @@ +""" +Updates the pyproject.toml schema_commit with what sendou.nik github repo has +""" + +import toml +import aiohttp +import asyncio + + +async def main(): + with open("../pyproject.toml", "r") as f: + toml_data = toml.load(f) + schema_commit = toml_data["tool"]["sendou-py"]["source"]["schema_commit"] + schema_path = toml_data["tool"]["sendou-py"]["source"]["schema_path"] + + async with aiohttp.ClientSession() as session: + async with session.get(f"https://api.github.com/repos/sendouc/sendou.ink/commits?path={schema_path}&per_page=1", headers={ + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }) as response: + if response.status != 200: + raise Exception("Failed to get response from Github API") + data = await response.json() + latest_sha = data[0]["sha"] + if latest_sha == schema_commit: + print("Schema is already up to date ❎") + exit(0) + toml_data["tool"]["sendou-py"]["source"]["schema_commit"] = latest_sha + + with open("../pyproject.toml", "w") as f: + toml.dump(toml_data, f) + + print("Updated schema_commit in pyproject.toml ✅") + print(f"Sha: {latest_sha}") + +asyncio.run(main()) diff --git a/sendou/client.py b/sendou/client.py index b73da1e..bc18e16 100644 --- a/sendou/client.py +++ b/sendou/client.py @@ -131,4 +131,4 @@ async def get_organization(self, organization_id: str) -> Optional[Organization] """ path = Organization.api_route(organization_id=organization_id) data = await self.__client.get_response(path) - return Organization(data, self.__client) + return Organization.from_dict(data, self.__client) diff --git a/sendou/models/organization.py b/sendou/models/organization.py index 70b46e6..d388ed5 100644 --- a/sendou/models/organization.py +++ b/sendou/models/organization.py @@ -37,13 +37,24 @@ class OrganizationMember(BaseModel): role: OrganizationRole role_display_name: Optional[str] - def __init__(self, data: dict, request_client: RequestsClient): + def __init__(self, data: dict, user_id: int, name: str, discord_id: str, role: OrganizationRole, + role_display_name: Optional[str], request_client: RequestsClient): super().__init__(data, request_client) - self.user_id = data.get("userId") - self.name = data.get("name") - self.discord_id = data.get("discordId") - self.role = OrganizationRole(data.get("role")) - self.role_display_name = data.get("roleDisplayName", None) + self.user_id = user_id + self.name = name + self.discord_id = discord_id + self.role = role + self.role_display_name = role_display_name + + @classmethod + def from_dict(cls, data: dict, request_client: RequestsClient): + user_id = data.get("userId") + name = data.get("name") + discord_id = data.get("discordId") + role = OrganizationRole(data.get("role")) + role_display_name = data.get("roleDisplayName", None) + return cls(data, user_id, name, discord_id, role, role_display_name, request_client) + async def get_user(self) -> Optional[User]: """ @@ -78,13 +89,39 @@ class Organization(BaseModel): social_link_urls: List[str] members: List[OrganizationMember] - def __init__(self, data: dict, request_client: RequestsClient): + def __init__(self, data: dict, id: int, name: str, description: Optional[str], url: str, logo_url: Optional[str], + social_link_urls: List[str], members: List[OrganizationMember], request_client: RequestsClient): super().__init__(data, request_client) - self.id = data.get("id") - self.name = data.get("name") - self.description = data.get("description", None) - self.url = data.get("url") - self.logo_url = data.get("logoUrl", None) - self.social_link_urls = data.get("socialLinkUrls") - self.members = [OrganizationMember(member, request_client) for member in data.get("members")] + self.id = id + self.name = name + self.description = description + self.url = url + self.logo_url = logo_url + self.social_link_urls = social_link_urls + self.members = members + + @classmethod + def from_dict(cls, data: dict, request_client: RequestsClient): + id = data.get("id") + name = data.get("name") + description = data.get("description", None) + url = data.get("url") + logo_url = data.get("logoUrl", None) + social_link_urls = data.get("socialLinkUrls") + members = [OrganizationMember.from_dict(member, request_client) for member in data.get("members")] + return cls(data, id, name, description, url, logo_url, social_link_urls, members, request_client) + + @staticmethod + def api_route(**kwargs) -> str: + + """ + Get the API route for organizations + + Kwargs: + org_id: Organization ID + + Returns: + (str): API route + """ + return f"/api/org/{kwargs.get('org_id')}" diff --git a/sendou/models/tournament/tournament.py b/sendou/models/tournament/tournament.py index b6e2da9..cd69166 100644 --- a/sendou/models/tournament/tournament.py +++ b/sendou/models/tournament/tournament.py @@ -10,6 +10,7 @@ from sendou.requests import RequestsClient from .bracket import Bracket, BracketType, BracketStanding from .team import TournamentTeam +from sendou.models.organization import Organization class TournamentTeamInfo: @@ -134,6 +135,19 @@ async def get_teams(self) -> List[TournamentTeam]: data = await self._request_client.get_response(path) return [TournamentTeam(team, self._request_client) for team in data] + async def get_organization(self) -> Optional[Organization]: + """ + Get the organization for the tournament + + Returns: + (Optional[Organization]): Organization (None if not found) + """ + if self.organization_id is None: + return None + path = Organization.api_route(organization_id=self.organization_id) + data = await self._request_client.get_response(path=path) + return Organization.from_dict(data, self._request_client) + @staticmethod def api_route(**kwargs) -> str: """ diff --git a/tests/sendou/models/test_organization.py b/tests/sendou/models/test_organization.py new file mode 100644 index 0000000..b99803e --- /dev/null +++ b/tests/sendou/models/test_organization.py @@ -0,0 +1,56 @@ +from sendou.models.organization import Organization, OrganizationMember, OrganizationRole +from sendou.requests import RequestsClient +from unittest.mock import MagicMock + +def test_organization_model(): + data = { + "id": 1, + "name": "Test Organization", + "description": "This is a test organization", + "url": "https://sendou.ink/org/test", + "logoUrl":"https://cdn.sendou.ink/org/inkling-performance-labs", + "socialLinkUrls": ["https://x.com/IPLSplatoon"], + "members": [ + { + "userId": 1, + "name": "Test User", + "discordId": "211187184001231608", + "role": "member", + "roleDisplayName": "display_name" + } + ] + } + organization = Organization.from_dict(data, MagicMock(RequestsClient)) + assert organization.id == 1 + assert organization.name == "Test Organization" + assert organization.description == "This is a test organization" + assert organization.url == "https://sendou.ink/org/test" + assert organization.logo_url == "https://cdn.sendou.ink/org/inkling-performance-labs" + assert organization.social_link_urls == ["https://x.com/IPLSplatoon"] + assert len(organization.members) == 1 + assert organization.members[0].user_id == 1 + assert organization.members[0].name == "Test User" + assert organization.members[0].role == OrganizationRole.MEMBER + assert organization.members[0].discord_id == "211187184001231608" + assert organization.members[0].role_display_name == "display_name" + +def test_organization_member_model(): + data = { + "userId": 1, + "name": "Test User", + "discordId": "211187184001231608", + "role": "member", + "roleDisplayName": "display_name" + } + member = OrganizationMember.from_dict(data, MagicMock(RequestsClient)) + assert member.user_id == 1 + assert member.name == "Test User" + assert member.role == OrganizationRole.MEMBER + assert member.discord_id == "211187184001231608" + assert member.role_display_name == "display_name" + +def test_organization_role_model(): + assert OrganizationRole.MEMBER.value == "member" + assert OrganizationRole.ADMIN.value == "admin" + assert OrganizationRole.ORGANIZER.value == "organizer" + assert OrganizationRole.STREAMER.value == "streamer" \ No newline at end of file From 960835456c1be379dddaac67bdbd0b5d9f303b95 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:08:36 +0100 Subject: [PATCH 03/17] update workflow --- .github/workflows/publish-to-pypi.yml | 4 +++- .github/workflows/pull_request.yml | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index d2fb31e..e52ef04 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -28,7 +28,9 @@ jobs: - name: Install dependencies run: poetry install --no-interaction --no-root --with=dev - name: Check Schema Hash with upstream - run: python scripts/checkUpstreamSchema.py + run: | + source .venv/bin/activate + python scripts/checkUpstreamSchema.py - name: Mint token id: mint uses: tschm/token-mint-action@v1.0.2 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3b98991..94cc93f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,9 +27,15 @@ jobs: installer-parallel: true - name: Install dependencies run: poetry install --no-interaction --no-root --with=dev + - name: Activate virtualenv + run: | + . .venv/bin/activate + echo PATH=$PATH >> $GITHUB_ENV - name: Check Schema Hash with upstream run: python scripts/checkUpstreamSchema.py - - name: Run pytest + - name: Install library + run: poetry install --no-interaction + - name: Run Pytest uses: pavelzw/pytest-action@v2 with: verbose: false From 617ea90415bf16172ebb99076653508fd36f5055 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:19:46 +0100 Subject: [PATCH 04/17] workflow update for venv --- .github/workflows/pull_request.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 94cc93f..3fd5625 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,6 +19,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' + cache: poetry - name: Install Poetry uses: snok/install-poetry@v1 with: @@ -29,7 +30,7 @@ jobs: run: poetry install --no-interaction --no-root --with=dev - name: Activate virtualenv run: | - . .venv/bin/activate + source .venv/bin/activate echo PATH=$PATH >> $GITHUB_ENV - name: Check Schema Hash with upstream run: python scripts/checkUpstreamSchema.py From f6ee505d2c3fb3ce8c421a93f36238a77c27cd31 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:20:31 +0100 Subject: [PATCH 05/17] remove poetry cache --- .github/workflows/pull_request.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3fd5625..51755ab 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,7 +19,6 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' - cache: poetry - name: Install Poetry uses: snok/install-poetry@v1 with: From ece897e23c2947901cd68aa6ecac63013ad11366 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:23:17 +0100 Subject: [PATCH 06/17] activate source --- .github/workflows/pull_request.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 51755ab..df4e8db 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,15 +27,14 @@ jobs: installer-parallel: true - name: Install dependencies run: poetry install --no-interaction --no-root --with=dev - - name: Activate virtualenv - run: | - source .venv/bin/activate - echo PATH=$PATH >> $GITHUB_ENV - name: Check Schema Hash with upstream run: python scripts/checkUpstreamSchema.py - name: Install library - run: poetry install --no-interaction + run: | + source .venv/bin/activate + poetry install --no-interaction - name: Run Pytest + run: source .venv/bin/activate uses: pavelzw/pytest-action@v2 with: verbose: false From 9e60b3ef98d72f2530f9a95e678e21f7212c03a2 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:24:47 +0100 Subject: [PATCH 07/17] remove invalid action syntax --- .github/workflows/pull_request.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index df4e8db..d87f441 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -34,7 +34,6 @@ jobs: source .venv/bin/activate poetry install --no-interaction - name: Run Pytest - run: source .venv/bin/activate uses: pavelzw/pytest-action@v2 with: verbose: false From bd752b5b207835548182e8cf47e1d277b1f52a95 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:26:33 +0100 Subject: [PATCH 08/17] activate venv before checking schema --- .github/workflows/pull_request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d87f441..909cbce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -26,13 +26,13 @@ jobs: virtualenvs-in-project: true installer-parallel: true - name: Install dependencies - run: poetry install --no-interaction --no-root --with=dev + run: poetry install --no-interaction --no-root --with=dev - name: Check Schema Hash with upstream - run: python scripts/checkUpstreamSchema.py - - name: Install library run: | source .venv/bin/activate - poetry install --no-interaction + python scripts/checkUpstreamSchema.py + - name: Install library + run: poetry install --no-interaction - name: Run Pytest uses: pavelzw/pytest-action@v2 with: From ec59f8f18bb4150858cd5d77d5a732b990adb6fe Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:31:26 +0100 Subject: [PATCH 09/17] checking working dir --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 909cbce..da5add2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,10 @@ jobs: installer-parallel: true - name: Install dependencies run: poetry install --no-interaction --no-root --with=dev + - name: Get directory + run: ls - name: Check Schema Hash with upstream + working-directory: sendou.py run: | source .venv/bin/activate python scripts/checkUpstreamSchema.py From 89f57762d91222fd7596de7ec87a4e146ec7b266 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:33:56 +0100 Subject: [PATCH 10/17] fix file path to pyproject for scripts --- .github/workflows/pull_request.yml | 3 --- scripts/checkUpstreamSchema.py | 2 +- scripts/updateUpstreamSchema.py | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index da5add2..909cbce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,10 +27,7 @@ jobs: installer-parallel: true - name: Install dependencies run: poetry install --no-interaction --no-root --with=dev - - name: Get directory - run: ls - name: Check Schema Hash with upstream - working-directory: sendou.py run: | source .venv/bin/activate python scripts/checkUpstreamSchema.py diff --git a/scripts/checkUpstreamSchema.py b/scripts/checkUpstreamSchema.py index 368adb1..7f0dba7 100644 --- a/scripts/checkUpstreamSchema.py +++ b/scripts/checkUpstreamSchema.py @@ -12,7 +12,7 @@ async def main(): # Open TOML and get data - with open("../pyproject.toml", "rb") as f: + with open("./pyproject.toml", "rb") as f: data = tomllib.load(f) schema_commit = data["tool"]["sendou-py"]["source"]["schema_commit"] schema_path = data["tool"]["sendou-py"]["source"]["schema_path"] diff --git a/scripts/updateUpstreamSchema.py b/scripts/updateUpstreamSchema.py index 5a88c49..f6cb0df 100644 --- a/scripts/updateUpstreamSchema.py +++ b/scripts/updateUpstreamSchema.py @@ -8,7 +8,7 @@ async def main(): - with open("../pyproject.toml", "r") as f: + with open("./pyproject.toml", "r") as f: toml_data = toml.load(f) schema_commit = toml_data["tool"]["sendou-py"]["source"]["schema_commit"] schema_path = toml_data["tool"]["sendou-py"]["source"]["schema_path"] From fcb0ef151bfaa1762e0372e8c89ea9f08cf5375d Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:41:16 +0100 Subject: [PATCH 11/17] generate test report --- .github/workflows/pull_request.yml | 23 +++- poetry.lock | 198 ++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 214 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 909cbce..254149c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,6 +11,8 @@ on: jobs: Pull-Request: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Check out repository uses: actions/checkout@v4 @@ -33,11 +35,18 @@ jobs: python scripts/checkUpstreamSchema.py - name: Install library run: poetry install --no-interaction - - name: Run Pytest - uses: pavelzw/pytest-action@v2 + - name: Run tests + env: + REPORT_OUTPUT: md_report.md + shell: bash + run: | + source .venv/bin/activate + echo "REPORT_FILE=${REPORT_OUTPUT}" >> "$GITHUB_ENV" + pytest -v --md-report --md-report-flavor gfm --md-report-exclude-outcomes passed skipped xpassed --md-report-output "$REPORT_OUTPUT" + - name: Render the report to the PR when tests fail + uses: marocchino/sticky-pull-request-comment@v2 + if: failure() with: - verbose: false - emoji: false - job-summary: true - click-to-expand: true - report-title: 'Test Report' + header: test-report + recreate: true + path: ${{ env.REPORT_FILE }} diff --git a/poetry.lock b/poetry.lock index 4e6be04..c553682 100644 --- a/poetry.lock +++ b/poetry.lock @@ -191,6 +191,17 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -202,6 +213,25 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataproperty" +version = "1.0.1" +description = "Python library for extract property from data." +optional = false +python-versions = ">=3.7" +files = [ + {file = "DataProperty-1.0.1-py3-none-any.whl", hash = "sha256:0b8b07d4fb6453fcf975b53d35dea41f3cfd69c9d79b5010c3cf224ff0407a7a"}, + {file = "DataProperty-1.0.1.tar.gz", hash = "sha256:723e5729fa6e885e127a771a983ee1e0e34bb141aca4ffe1f0bfa7cde34650a4"}, +] + +[package.dependencies] +mbstrdecoder = ">=1.0.0,<2" +typepy = {version = ">=1.2.0,<2", extras = ["datetime"]} + +[package.extras] +logging = ["loguru (>=0.4.1,<1)"] +test = ["pytest (>=6.0.1)", "pytest-md-report (>=0.3)", "tcolorpy (>=0.1.2)"] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -335,6 +365,23 @@ files = [ {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] +[[package]] +name = "mbstrdecoder" +version = "1.1.3" +description = "mbstrdecoder is a Python library for multi-byte character string decoder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mbstrdecoder-1.1.3-py3-none-any.whl", hash = "sha256:d66c1ed3f2dc4e7c5d87cd44a75be10bc5af4250f95b38bbaedd7851308ce938"}, + {file = "mbstrdecoder-1.1.3.tar.gz", hash = "sha256:dcfd2c759322eb44fe193a9e0b1b86c5b87f3ec5ea8e1bb43b3e9ae423f1e8fe"}, +] + +[package.dependencies] +chardet = ">=3.0.4,<6" + +[package.extras] +test = ["Faker (>=1.0.2)", "pytest (>=6.0.1)", "pytest-md-report (>=0.1)"] + [[package]] name = "multidict" version = "6.0.5" @@ -445,6 +492,21 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pathvalidate" +version = "3.2.0" +description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathvalidate-3.2.0-py3-none-any.whl", hash = "sha256:cc593caa6299b22b37f228148257997e2fa850eea2daf7e4cc9205cef6908dee"}, + {file = "pathvalidate-3.2.0.tar.gz", hash = "sha256:5e8378cf6712bff67fbe7a8307d99fa8c1a0cb28aa477056f8fc374f0dff24ad"}, +] + +[package.extras] +docs = ["Sphinx (>=2.4)", "sphinx-rtd-theme (>=1.2.2)", "urllib3 (<2)"] +test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-discord (>=0.1.4)", "pytest-md-report (>=0.4.1)"] + [[package]] name = "pluggy" version = "1.5.0" @@ -460,6 +522,42 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pytablewriter" +version = "1.2.0" +description = "pytablewriter is a Python library to write a table in various formats: AsciiDoc / CSV / Elasticsearch / HTML / JavaScript / JSON / LaTeX / LDJSON / LTSV / Markdown / MediaWiki / NumPy / Excel / Pandas / Python / reStructuredText / SQLite / TOML / TSV / YAML." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytablewriter-1.2.0-py3-none-any.whl", hash = "sha256:4a30e2bb4bf5bc1069b1d2b2bc41947577c4517ab0875b23a5b194d296f543d8"}, + {file = "pytablewriter-1.2.0.tar.gz", hash = "sha256:0204a4bb684a22140d640f2599f09e137bcdc18b3dd49426f4a555016e246b46"}, +] + +[package.dependencies] +DataProperty = ">=1.0.1,<2" +mbstrdecoder = ">=1.0.0,<2" +pathvalidate = ">=2.3.0,<4" +setuptools = ">=38.3.0" +tabledata = ">=1.3.1,<2" +tcolorpy = ">=0.0.5,<1" +typepy = {version = ">=1.3.2,<2", extras = ["datetime"]} + +[package.extras] +all = ["PyYAML (>=3.11,<7)", "SimpleSQLite (>=1.3.2,<2)", "XlsxWriter (>=0.9.6,<4)", "dominate (>=2.1.5,<3)", "elasticsearch (>=8.0.1,<9)", "loguru (>=0.4.1,<1)", "pandas (>=0.25.3,<3)", "pytablereader (>=0.31.3,<2)", "pytablewriter-altcol-theme (>=0.1.0,<1)", "pytablewriter-altrow-theme (>=0.2.0,<1)", "simplejson (>=3.8.1,<4)", "toml (>=0.9.3,<1)", "xlwt"] +docs = ["PyYAML (>=3.11,<7)", "SimpleSQLite (>=1.3.2,<2)", "Sphinx (>=2.4)", "XlsxWriter (>=0.9.6,<4)", "dominate (>=2.1.5,<3)", "elasticsearch (>=8.0.1,<9)", "loguru (>=0.4.1,<1)", "pandas (>=0.25.3,<3)", "pytablereader (>=0.31.3,<2)", "pytablewriter-altcol-theme (>=0.1.0,<1)", "pytablewriter-altrow-theme (>=0.2.0,<1)", "simplejson (>=3.8.1,<4)", "sphinx-rtd-theme (>=1.2.2)", "toml (>=0.9.3,<1)", "xlwt"] +es = ["elasticsearch (>=8.0.1,<9)"] +es8 = ["elasticsearch (>=8.0.1,<9)"] +excel = ["XlsxWriter (>=0.9.6,<4)", "xlwt"] +from = ["pytablereader (>=0.31.3,<2)"] +html = ["dominate (>=2.1.5,<3)"] +logging = ["loguru (>=0.4.1,<1)"] +pandas = ["pandas (>=0.25.3,<3)"] +sqlite = ["SimpleSQLite (>=1.3.2,<2)"] +test = ["PyYAML (>=3.11,<7)", "SimpleSQLite (>=1.3.2,<2)", "XlsxWriter (>=0.9.6,<4)", "beautifulsoup4 (>=4.10)", "dominate (>=2.1.5,<3)", "elasticsearch (>=8.0.1,<9)", "loguru (>=0.4.1,<1)", "pandas (>=0.25.3,<3)", "pytablereader (>=0.31.3,<2)", "pytablereader[excel,sqlite] (>=0.31.3)", "pytablewriter-altcol-theme (>=0.1.0,<1)", "pytablewriter-altrow-theme (>=0.2.0,<1)", "pytest (>=6.0.1)", "pytest-md-report (>=0.4.1)", "simplejson (>=3.8.1,<4)", "sqliteschema (>=1.3.0)", "tablib (>=3.2.0)", "toml (>=0.9.3,<1)", "xlwt"] +theme = ["pytablewriter-altcol-theme (>=0.1.0,<1)", "pytablewriter-altrow-theme (>=0.2.0,<1)"] +toml = ["toml (>=0.9.3,<1)"] +yaml = ["PyYAML (>=3.11,<7)"] + [[package]] name = "pytest" version = "8.3.2" @@ -482,6 +580,23 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-md-report" +version = "0.6.2" +description = "A pytest plugin to generate test outcomes reports with markdown table format." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest_md_report-0.6.2-py3-none-any.whl", hash = "sha256:66e27efa5c155c87eb4700d60876e61a85c13361448c4031fda964c43e63c9b9"}, + {file = "pytest_md_report-0.6.2.tar.gz", hash = "sha256:5e96c655ebc9b5c3c7b78bf7c5382c1f68056e96904430252790f8737de5ce99"}, +] + +[package.dependencies] +pytablewriter = ">=1.2.0,<2" +pytest = ">=3.3.2,<6.0.0 || >6.0.0,<9" +tcolorpy = ">=0.0.5,<1" +typepy = ">=1.1.1,<2" + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -496,6 +611,33 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "setuptools" +version = "72.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"}, + {file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -507,6 +649,39 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "tabledata" +version = "1.3.3" +description = "tabledata is a Python library to represent tabular data. Used for pytablewriter/pytablereader/SimpleSQLite/etc." +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabledata-1.3.3-py3-none-any.whl", hash = "sha256:4abad1c996d8607e23b045b44dc0c5f061668f3c37585302c5f6c84c93a89962"}, + {file = "tabledata-1.3.3.tar.gz", hash = "sha256:c90daaba9a408e4397934b3ff2f6c06797d5289676420bf520c741ad43e6ff91"}, +] + +[package.dependencies] +DataProperty = ">=1.0.1,<2" +typepy = ">=1.2.0,<2" + +[package.extras] +logging = ["loguru (>=0.4.1,<1)"] +test = ["pytablewriter (>=0.46)", "pytest"] + +[[package]] +name = "tcolorpy" +version = "0.1.6" +description = "tcolopy is a Python library to apply true color for terminal text." +optional = false +python-versions = ">=3.7" +files = [ + {file = "tcolorpy-0.1.6-py3-none-any.whl", hash = "sha256:8c15cb3167f30b0a433d72297e9d68667c825bd9e2af41c8dd7dfbd3d7f7e207"}, + {file = "tcolorpy-0.1.6.tar.gz", hash = "sha256:8cea0bf5f8cf03f77528a9acfbf312df935573892ba5ea3b2516e61fa54de9a5"}, +] + +[package.extras] +test = ["pytest (>=6.0.1)", "pytest-md-report (>=0.5)"] + [[package]] name = "toml" version = "0.10.2" @@ -529,6 +704,27 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "typepy" +version = "1.3.2" +description = "typepy is a Python library for variable type checker/validator/converter at a run time." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typepy-1.3.2-py3-none-any.whl", hash = "sha256:d5d1022a424132622993800f1d2cd16cfdb691ac4e3b9c325f0fcb37799db1ae"}, + {file = "typepy-1.3.2.tar.gz", hash = "sha256:b69fd48b9f50cdb3809906eef36b855b3134ff66c8893a4f8580abddb0b39517"}, +] + +[package.dependencies] +mbstrdecoder = ">=1.0.0,<2" +packaging = {version = "*", optional = true, markers = "extra == \"datetime\""} +python-dateutil = {version = ">=2.8.0,<3.0.0", optional = true, markers = "extra == \"datetime\""} +pytz = {version = ">=2018.9", optional = true, markers = "extra == \"datetime\""} + +[package.extras] +datetime = ["packaging", "python-dateutil (>=2.8.0,<3.0.0)", "pytz (>=2018.9)"] +test = ["packaging", "pytest (>=6.0.1)", "python-dateutil (>=2.8.0,<3.0.0)", "pytz (>=2018.9)", "tcolorpy"] + [[package]] name = "url-normalize" version = "1.4.3" @@ -649,4 +845,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b85360b0e8bbaf8bd2665a05633f6eb637a5fe385ad20e331cfac3c9293d2880" +content-hash = "f90825d9e5797bac2ac1c120e03606d2decc062523af162562c28cc43317401c" diff --git a/pyproject.toml b/pyproject.toml index dc83f93..7cd1373 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,3 +39,4 @@ testpaths = ["tests"] pytest = "^8.3.2" asyncio = "^3.4.3" toml = "^0.10.2" +pytest-md-report = "^0.6.2" From 502e13b3fb7edde7445635debcd80a63231d0fc4 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:48:38 +0100 Subject: [PATCH 12/17] Save test report from CI --- .github/workflows/publish-to-pypi.yml | 19 +++++++++++++++++++ .github/workflows/pull_request.yml | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e52ef04..792e84b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -31,6 +31,25 @@ jobs: run: | source .venv/bin/activate python scripts/checkUpstreamSchema.py + - name: Run tests + env: + REPORT_OUTPUT: md_report.md + shell: bash + run: | + source .venv/bin/activate + echo "REPORT_FILE=${REPORT_OUTPUT}" >> "$GITHUB_ENV" + pytest -v --md-report --md-report-flavor gfm --md-report-exclude-outcomes passed skipped xpassed --md-report-output "$REPORT_OUTPUT" + - name: Output reports to the job summary when tests fail + if: failure() + shell: bash + run: | + if [ -f "$REPORT_FILE" ]; then + echo "
Failed Test Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat "$REPORT_FILE" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + fi - name: Mint token id: mint uses: tschm/token-mint-action@v1.0.2 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 254149c..f23a002 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -50,3 +50,13 @@ jobs: header: test-report recreate: true path: ${{ env.REPORT_FILE }} + - name: Output reports to the job summary + shell: bash + run: | + if [ -f "$REPORT_FILE" ]; then + echo "
Test Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat "$REPORT_FILE" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + fi From 71477f5cb2cf4ae9c8cb0897a0b977cf4fd519bf Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Thu, 15 Aug 2024 23:55:07 +0100 Subject: [PATCH 13/17] Separate out CI dependencies --- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/pull_request.yml | 12 +----------- poetry.lock | 2 +- pyproject.toml | 4 +++- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 792e84b..60bddb6 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,7 +26,7 @@ jobs: virtualenvs-in-project: true installer-parallel: true - name: Install dependencies - run: poetry install --no-interaction --no-root --with=dev + run: poetry install --no-interaction --no-root --with=dev,ci - name: Check Schema Hash with upstream run: | source .venv/bin/activate diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f23a002..ed2b200 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -49,14 +49,4 @@ jobs: with: header: test-report recreate: true - path: ${{ env.REPORT_FILE }} - - name: Output reports to the job summary - shell: bash - run: | - if [ -f "$REPORT_FILE" ]; then - echo "
Test Report" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - cat "$REPORT_FILE" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - fi + path: ${{ env.REPORT_FILE }} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index c553682..f1c8a63 100644 --- a/poetry.lock +++ b/poetry.lock @@ -845,4 +845,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f90825d9e5797bac2ac1c120e03606d2decc062523af162562c28cc43317401c" +content-hash = "566a193b5acc86f73cab407847a1d29d2b645d234be69933fb32b9b163b1d11c" diff --git a/pyproject.toml b/pyproject.toml index 7cd1373..4907c77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,4 +39,6 @@ testpaths = ["tests"] pytest = "^8.3.2" asyncio = "^3.4.3" toml = "^0.10.2" -pytest-md-report = "^0.6.2" + +[tool.poetry.group.ci.dependencies] +pytest-md-report = "^0.6.2" \ No newline at end of file From d4a83bdd9eb194d6d7efbca391c08c7bdcd2daf8 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Fri, 16 Aug 2024 00:17:47 +0100 Subject: [PATCH 14/17] Update readme --- .github/workflows/pull_request.yml | 2 +- README.md | 45 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ed2b200..be43ba4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -28,7 +28,7 @@ jobs: virtualenvs-in-project: true installer-parallel: true - name: Install dependencies - run: poetry install --no-interaction --no-root --with=dev + run: poetry install --no-interaction --no-root --with=dev,ci - name: Check Schema Hash with upstream run: | source .venv/bin/activate diff --git a/README.md b/README.md index 162f331..e9682af 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,48 @@ asyncio.run(run()) ## Getting an API Key To use this library, you must have an API key. You need to DM Sendou for an API Key currently. +## Development +For development, you'll need [Poetry](https://python-poetry.org) installed for dependency management and building distributions + +### Dev Dependencies +When install dependencies for development run + +```bash +poetry install --with=dev +``` + +*In CI you way want to run `poetry install --with=dev,ci` that includes CI dependencies for GitHub Actions* + +### Testing +This package has *some* tests, these are written with pytest and can be run with + +```bash +pytest +``` + +*You likely need to run `poetry install` before executing pytest* + +### Tracking Upstream Schema +This package uses sendou.ink's [Public API Schema](https://github.com/Sendouc/sendou.ink/blob/rewrite/app/features/api-public/schema.ts) +file to design the models uses in the package. To keep track of where the package is in relation to the upstream schema, +the commit sha of the upstream schema is kept in the `pyproject.toml` file under `tool.sendou-py.source`. + +There are 2 scripts that help keep this package inline with the upstream schema. + +#### Upstream Schema Commit SHA checker +This script uses the GitHub API to check that the SHA stored in `tool.sendou-py.source` matches the latest commit for +for the upstream schema. + +```bash +python3 python3 scripts/checkUpstreamSchema.py +``` + + +#### Update local SHA with Upstream Schema Commit SHA +This script pulls down the latest SHA hash for the upstream schema and saves it to the `pyproject.toml` file + +```bash +python3 scripts/updateUpstreamSchema.py +``` + +**This should only be run after dev has checked their changes match the upstream schema** \ No newline at end of file From b438b285cb38742c6eb5df21780c687797dac9c7 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Fri, 13 Sep 2024 17:47:12 +0100 Subject: [PATCH 15/17] refractor bracket standing and fix fetching standings --- sendou/models/tournament/bracket/Standing.py | 44 +++++++++++++++----- sendou/models/tournament/tournament.py | 4 +- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/sendou/models/tournament/bracket/Standing.py b/sendou/models/tournament/bracket/Standing.py index a656bfe..d3dbe7f 100644 --- a/sendou/models/tournament/bracket/Standing.py +++ b/sendou/models/tournament/bracket/Standing.py @@ -27,15 +27,39 @@ class StandingStats: buchholz_sets: Optional[int] buchholz_maps: Optional[int] - def __init__(self, data: dict): - self.set_wins = data.get("setWins", 0) - self.set_loses = data.get("setLoses", 0) - self.map_wins = data.get("mapWins", 0) - self.map_loses = data.get("mapLoses", 0) - self.points = data.get("points", 0) - self.wins_against_tied = data.get("winsAgainstTied", 0) - self.buchholz_sets = data.get("buchholzSets", None) - self.buchholz_maps = data.get("buchholzMaps", None) + def __init__(self, set_wins: int, set_loses: int, map_wins: int, map_loses: int, points: int, wins_against_tied: int, + buchholz_sets: Optional[int] = None, buchholz_maps: Optional[int] = None): + + self.set_wins = set_wins + self.set_loses = set_loses + self.map_wins = map_wins + self.map_loses = map_loses + self.points = points + self.wins_against_tied = wins_against_tied + self.buchholz_maps = buchholz_maps + self.buchholz_sets = buchholz_sets + + @classmethod + def from_dict(cls, data: dict): + """ + Returns a StandingStats object from a dictionary + + Args: + data (dict): Dictionary + + Returns: + StandingStats: StandingStats object + """ + return cls( + set_wins=data.get("setWins", 0), + set_loses=data.get("setLoses", 0), + map_wins=data.get("mapWins", 0), + map_loses=data.get("mapLoses", 0), + points=data.get("points", 0), + wins_against_tied=data.get("winsAgainstTied", 0), + buchholz_sets=data.get("buchholzSets", None), + buchholz_maps=data.get("buchholzMaps", None) + ) class BracketStanding: @@ -54,7 +78,7 @@ class BracketStanding: def __init__(self, data: dict): self.tournament_team_id = data.get("tournamentTeamId", 0) self.placement = data.get("placement", 0) - self.stats = StandingStats(data.get("stats", {})) + self.stats = StandingStats.from_dict(data.get("stats", {})) @staticmethod def api_route(**kwargs) -> str: diff --git a/sendou/models/tournament/tournament.py b/sendou/models/tournament/tournament.py index cd69166..c850ee0 100644 --- a/sendou/models/tournament/tournament.py +++ b/sendou/models/tournament/tournament.py @@ -76,9 +76,9 @@ async def get_standings(self) -> List[BracketStanding]: Returns: (List[BracketStanding]): List of Bracket Standings """ - path = Bracket.api_route(tournament_id=self.__tournament_id, bracket_index=self._index) + path = BracketStanding.api_route(tournament_id=self.__tournament_id, bracket_index=self._index) data = await self._request_client.get_response(path) - return [BracketStanding(standing) for standing in data] + return [BracketStanding(standing) for standing in data["standings"]] class Tournament(BaseModel): From c6154bac59a3dd1e5ef3fc03206482ee678d13d9 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Fri, 13 Sep 2024 18:07:31 +0100 Subject: [PATCH 16/17] Get org data working --- sendou/models/organization.py | 10 +++++----- sendou/models/tournament/tournament.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sendou/models/organization.py b/sendou/models/organization.py index d388ed5..346f2a5 100644 --- a/sendou/models/organization.py +++ b/sendou/models/organization.py @@ -14,10 +14,10 @@ class OrganizationRole(Enum): """ Represents a role of member in the organization """ - ADMIN = "admin" - MEMBER = "member" - ORGANIZER = "organizer" - STREAMER = "streamer" + admin = "ADMIN" + member = "MEMBER" + organizer = "ORGANIZER" + streamer = "STREAMER" class OrganizationMember(BaseModel): @@ -123,5 +123,5 @@ def api_route(**kwargs) -> str: Returns: (str): API route """ - return f"/api/org/{kwargs.get('org_id')}" + return f"api/org/{kwargs.get('org_id')}" diff --git a/sendou/models/tournament/tournament.py b/sendou/models/tournament/tournament.py index c850ee0..b3390ab 100644 --- a/sendou/models/tournament/tournament.py +++ b/sendou/models/tournament/tournament.py @@ -144,7 +144,7 @@ async def get_organization(self) -> Optional[Organization]: """ if self.organization_id is None: return None - path = Organization.api_route(organization_id=self.organization_id) + path = Organization.api_route(org_id=self.organization_id) data = await self._request_client.get_response(path=path) return Organization.from_dict(data, self._request_client) From be0aadb41ddadc009c541fa449e2c4787003c06b Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Fri, 13 Sep 2024 20:20:10 +0100 Subject: [PATCH 17/17] Fixing existing unit test --- tests/sendou/models/test_organization.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/sendou/models/test_organization.py b/tests/sendou/models/test_organization.py index b99803e..6eff130 100644 --- a/tests/sendou/models/test_organization.py +++ b/tests/sendou/models/test_organization.py @@ -15,7 +15,7 @@ def test_organization_model(): "userId": 1, "name": "Test User", "discordId": "211187184001231608", - "role": "member", + "role": "MEMBER", "roleDisplayName": "display_name" } ] @@ -30,7 +30,7 @@ def test_organization_model(): assert len(organization.members) == 1 assert organization.members[0].user_id == 1 assert organization.members[0].name == "Test User" - assert organization.members[0].role == OrganizationRole.MEMBER + assert organization.members[0].role == OrganizationRole.member assert organization.members[0].discord_id == "211187184001231608" assert organization.members[0].role_display_name == "display_name" @@ -39,18 +39,18 @@ def test_organization_member_model(): "userId": 1, "name": "Test User", "discordId": "211187184001231608", - "role": "member", + "role": "MEMBER", "roleDisplayName": "display_name" } member = OrganizationMember.from_dict(data, MagicMock(RequestsClient)) assert member.user_id == 1 assert member.name == "Test User" - assert member.role == OrganizationRole.MEMBER + assert member.role == OrganizationRole.member assert member.discord_id == "211187184001231608" assert member.role_display_name == "display_name" def test_organization_role_model(): - assert OrganizationRole.MEMBER.value == "member" - assert OrganizationRole.ADMIN.value == "admin" - assert OrganizationRole.ORGANIZER.value == "organizer" - assert OrganizationRole.STREAMER.value == "streamer" \ No newline at end of file + assert OrganizationRole.member.value == "MEMBER" + assert OrganizationRole.admin.value == "ADMIN" + assert OrganizationRole.organizer.value == "ORGANIZER" + assert OrganizationRole.streamer.value == "STREAMER" \ No newline at end of file