From 3cc2456b684561e3d4e6782e63433740c5e25bd3 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Fri, 28 Jul 2023 16:06:06 +0200 Subject: [PATCH 01/26] improved iterative models added slicing to the iterative models --- pyclasher/bulk_requests/BulkRequestModel.py | 9 ++++++++- pyclasher/bulk_requests/BulkRequestModel.pyi | 4 ++-- pyclasher/models/BaseModels.py | 10 +++++++++- pyclasher/models/BaseModels.pyi | 4 ++-- pyclasher/requests/RequestModels.py | 10 +++++++++- pyclasher/requests/RequestModels.pyi | 4 ++-- 6 files changed, 32 insertions(+), 9 deletions(-) diff --git a/pyclasher/bulk_requests/BulkRequestModel.py b/pyclasher/bulk_requests/BulkRequestModel.py index 0425ef2..a79ab8c 100644 --- a/pyclasher/bulk_requests/BulkRequestModel.py +++ b/pyclasher/bulk_requests/BulkRequestModel.py @@ -1,5 +1,7 @@ from asyncio import gather, get_running_loop, run +from Exceptions import RequestNotDone + class BulkRequestModel: _request_model = ... @@ -34,7 +36,12 @@ def __len__(self): return len(self._requests) def __getitem__(self, item): - return self._requests[item] + self._requests[0].to_dict() # test if the `to_dict()` method raises `RequestNotDone` + if isinstance(item, int): + return self._requests[item] + if isinstance(item, slice): + return (self._requests[i] for i in range(*item.indices(len(self._requests)))) + raise NotImplementedError def __iter__(self): self._iter = iter(self._requests) diff --git a/pyclasher/bulk_requests/BulkRequestModel.pyi b/pyclasher/bulk_requests/BulkRequestModel.pyi index 1d8996a..f2fba2c 100644 --- a/pyclasher/bulk_requests/BulkRequestModel.pyi +++ b/pyclasher/bulk_requests/BulkRequestModel.pyi @@ -1,4 +1,4 @@ -from typing import Any, Coroutine, Iterator +from typing import Any, Coroutine, Iterator, Generator class BulkRequestModel: @@ -77,7 +77,7 @@ class BulkRequestModel: def __len__(self) -> int: ... - def __getitem__(self, item: int) -> _request_model: + def __getitem__(self, item: int | slice) -> Generator | _request_model: ... def __iter__(self) -> Iterator[_request_model]: diff --git a/pyclasher/models/BaseModels.py b/pyclasher/models/BaseModels.py index 0b4d995..62b03eb 100644 --- a/pyclasher/models/BaseModels.py +++ b/pyclasher/models/BaseModels.py @@ -69,7 +69,15 @@ def __len__(self): return self._len def __getitem__(self, item): - return self._iter_rtype(self._data[item]) + if self._data is MISSING: + raise RequestNotDone + if self._data is None: + return None + if isinstance(item, int): + return self._iter_rtype(self._data[item]) + if isinstance(item, slice): + return (self._iter_rtype(self._data[i]) for i in range(*item.indices(len(self._data)))) + raise NotImplementedError def __iter__(self): self._iter = iter(self._data) diff --git a/pyclasher/models/BaseModels.pyi b/pyclasher/models/BaseModels.pyi index df3a0b9..c3a319a 100644 --- a/pyclasher/models/BaseModels.pyi +++ b/pyclasher/models/BaseModels.pyi @@ -2,7 +2,7 @@ base models for this API wrapper client """ -from typing import Any, Iterator +from typing import Any, Iterator, Generator from ..Exceptions import MISSING, Missing @@ -123,7 +123,7 @@ class IterBaseModel: def __len__(self) -> int: ... - def __getitem__(self, item: int) -> _iter_rtype: + def __getitem__(self, item: int | slice) -> MISSING | Generator | _iter_rtype: ... def __iter__(self) -> Iterator[_iter_rtype]: diff --git a/pyclasher/requests/RequestModels.py b/pyclasher/requests/RequestModels.py index 239dd7b..5ca72b6 100644 --- a/pyclasher/requests/RequestModels.py +++ b/pyclasher/requests/RequestModels.py @@ -156,7 +156,15 @@ def paging(self): return Paging(self._get_data('paging')) def __getitem__(self, item): - return self._iter_rtype(self._get_data('items')[item]) + if self._data is MISSING: + raise RequestNotDone + if self._data is None: + return None + if isinstance(item, int): + return self.items[item] + if isinstance(item, slice): + return (self.items[i] for i in range(*item.indices(len(self)))) + raise NotImplementedError def __iter__(self): self._iter = iter(self._get_data('items')) diff --git a/pyclasher/requests/RequestModels.pyi b/pyclasher/requests/RequestModels.pyi index 8afb50c..8d7ccc7 100644 --- a/pyclasher/requests/RequestModels.pyi +++ b/pyclasher/requests/RequestModels.pyi @@ -1,4 +1,4 @@ -from typing import Any, Self, Coroutine, Iterator +from typing import Any, Self, Coroutine, Iterator, Generator from ..Exceptions import MISSING, Missing from ..client import PyClasherClient, RequestMethods @@ -79,7 +79,7 @@ class IterRequestModel(RequestModel): def paging(self) -> Paging: ... - def __getitem__(self, item: int) -> _iter_rtype: + def __getitem__(self, item: int | slice) -> Generator | _iter_rtype: ... def __iter__(self) -> Iterator[_iter_rtype]: From e52b211a3a312a66c20c02b946c20e642ab6ed62 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Fri, 28 Jul 2023 16:06:20 +0200 Subject: [PATCH 02/26] created build.sh --- build.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 build.sh diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..27ee70e --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +rm -r build dist pyclasher.egg-info + +python setup.py sdist bdist_wheel From 584bb0bffba5eb8638ffc1d696f20aea58483ade Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 12 Aug 2023 17:08:59 +0200 Subject: [PATCH 03/26] fix: `PlayerBulkRequest._async_from_clan` accepts `Clan`, `MemberList`, `str` fixed the `PlayerBulkRequest._async_from_clan` class method so that it accepts `Clan`, `MemberList`, `str` as arguments for the class initialisation --- pyclasher/bulk_requests/BulkPlayer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyclasher/bulk_requests/BulkPlayer.py b/pyclasher/bulk_requests/BulkPlayer.py index 11b08a8..1489ca4 100644 --- a/pyclasher/bulk_requests/BulkPlayer.py +++ b/pyclasher/bulk_requests/BulkPlayer.py @@ -1,5 +1,7 @@ from asyncio import get_running_loop, run +from Exceptions import MISSING +from models import Clan from .BulkRequestModel import BulkRequestModel from ..models import BaseClan from ..requests import PlayerRequest, ClanMembersRequest @@ -20,8 +22,12 @@ def tags(self): @classmethod async def _async_from_clan(cls, clan): - members = await (ClanMembersRequest(clan.tag).request() if isinstance(clan, BaseClan) else - await ClanMembersRequest(clan).request()) + if isinstance(clan, Clan) and clan.member_list is not MISSING: + members = clan.member_list + elif isinstance(clan, BaseClan): + members = await ClanMembersRequest(clan.tag).request() + else: + members = await ClanMembersRequest(clan).request() return cls.from_member_list(members) @classmethod From 47e57db6013445fc32ddd974c4008539192bd1b1 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 12 Aug 2023 17:17:44 +0200 Subject: [PATCH 04/26] style: line break after 120 characters in client.py --- pyclasher/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyclasher/client.py b/pyclasher/client.py index d09b216..826fd5f 100644 --- a/pyclasher/client.py +++ b/pyclasher/client.py @@ -204,7 +204,8 @@ async def _request(self, future, url, method, body, status, error): future.set_result(response_json) status.set_result(response.status) - error.set_result(None if response.status == 200 else ApiCodes.from_exception(response.status, response_json)) + error.set_result(None if response.status == 200 else + ApiCodes.from_exception(response.status, response_json)) return async def consume(self): From 367ddf79eca6ae49a95a55dee6c26c086462f6de Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 12 Aug 2023 17:18:44 +0200 Subject: [PATCH 05/26] fix: correct type hinting for `ApiCodes.from_exception` --- pyclasher/models/Enums.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyclasher/models/Enums.pyi b/pyclasher/models/Enums.pyi index 8dd9099..5879877 100644 --- a/pyclasher/models/Enums.pyi +++ b/pyclasher/models/Enums.pyi @@ -32,7 +32,7 @@ class ApiCodes(Enum): def from_exception(cls, code: int, response_json: dict - ) -> ApiCode: + ) -> ApiCodes: """ class method that allows to initialise ApiCodes with a failed request response From 7901fd039756b69b5c412c48fe62856b14c44a55 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 12 Aug 2023 17:21:48 +0200 Subject: [PATCH 06/26] feat: added a ToDo-list in the README.md added a ToDo-list for planned code implementations --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 8616f66..4fd91bc 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,14 @@ good as I can. - documentation - real example ([HeadhunterBot][headhunterbot_url] is in development) +### Planned code implementations (ToDo-list) + +- `total_wars` attribute for `ClanWarLogEntry` +- war log filtering for `ClanWarLogRequest` (for example filter the number of attacks per member) +- some "average" attributes for `Clan`-classes, `WarLog`-classes, ... +- attributes `king`, `queen`, `warden`, `royal_champion` for the `Player.heroes` attribute +- autosort for the `ClanCurrentWarRequest` members of the `member_list` attribute (sorted by the map position) + --- If you find a bug, an error or want custom functionality, please tell From 37683965e55bfa52916f4ffe72bdbfe3c3a672c7 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 12 Aug 2023 17:31:28 +0200 Subject: [PATCH 07/26] feat: added `__version` to `pyclasher` added `__version__` to the `pyclasher` `__init__.py` file --- pyclasher/__init__.py | 2 ++ setup.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyclasher/__init__.py b/pyclasher/__init__.py index 0b6e9e1..d5b25dc 100644 --- a/pyclasher/__init__.py +++ b/pyclasher/__init__.py @@ -9,6 +9,8 @@ .. author:: 201st-Luka """ +__version__ = '1.0.0-alpha1' + from .Exceptions import ApiCode, ClientIsRunning, ClientIsNotRunning, ClientAlreadyInitialised, NoClient, \ InvalidType, InvalidLoginData, LoginNotDone, NoneToken, RequestNotDone, InvalidTimeFormat, \ InvalidSeasonFormat, Missing, MISSING, PyClasherException diff --git a/setup.py b/setup.py index 8f39dfc..b76ae7c 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,19 @@ from setuptools import setup, find_packages +import pyclasher + + with open('requirements.txt') as requirements_txt: requirements = requirements_txt.read().splitlines() with open('README.md', 'r', encoding='utf-8') as readme_md: readme = readme_md.read() -VERSION = '1.0.0-alpha1' - -packages = find_packages(exclude=['tests', 'tests.*']) +packages = find_packages(where="pyclasher") setup( name='pyclasher', - version=VERSION, + version=pyclasher.__version__, author='201st-Luka', description='pyclasher - an object-oriented wrapper client for Python that provides easy access to the requested data', long_description=readme, From 948fe78aaf8993d51c2b3e5cf942044ae93e82d9 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:33:54 +0200 Subject: [PATCH 08/26] feat: added requirements_tests.txt added a requirements file to install pytest and other dependencies --- requirements_tests.txt | 3 +++ tests/asyncronous_tests/__init__.py | 0 2 files changed, 3 insertions(+) create mode 100644 requirements_tests.txt delete mode 100644 tests/asyncronous_tests/__init__.py diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 0000000..8fc17ba --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1,3 @@ +aiohttp>=3.8 +pytest>=7.4 +pytest-asyncio>=0.21 \ No newline at end of file diff --git a/tests/asyncronous_tests/__init__.py b/tests/asyncronous_tests/__init__.py deleted file mode 100644 index e69de29..0000000 From 46214d8eb2d46242ba9aa58d541f40e289fcb44c Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:35:43 +0200 Subject: [PATCH 09/26] fix: added `ClanWarResult.NONE` in the `Enums.py` file added `ClanWarResult.NONE` in the `Enums.py` file because the war state of CWL clan wars is None sometimes --- pyclasher/models/Enums.py | 1 + pyclasher/models/Enums.pyi | 1 + 2 files changed, 2 insertions(+) diff --git a/pyclasher/models/Enums.py b/pyclasher/models/Enums.py index 441877e..dc3ec13 100644 --- a/pyclasher/models/Enums.py +++ b/pyclasher/models/Enums.py @@ -606,6 +606,7 @@ class ClanWarResult(Enum): LOSE = "lose" WIN = "win" TIE = "tie" + NONE = None class ClanRole(Enum): diff --git a/pyclasher/models/Enums.pyi b/pyclasher/models/Enums.pyi index 5879877..6016b9b 100644 --- a/pyclasher/models/Enums.pyi +++ b/pyclasher/models/Enums.pyi @@ -510,6 +510,7 @@ class ClanWarResult(Enum): LOSE = "lose" WIN = "win" TIE = "tie" + NONE = None class ClanRole(Enum): From 5ae66e37f9be6408adb673d15f1f050d69d36391 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:36:53 +0200 Subject: [PATCH 10/26] tests: rewrite and improvement rewrite of the async clan tests to use more features of pytest to make the code simpler --- pyclasher/models/BaseModels.py | 5 + pyclasher/models/Clan.pyi | 14 +- pyclasher/models/ClanCapitalRaidSeasons.pyi | 3 +- pyclasher/models/ClanMember.pyi | 3 +- pyclasher/models/__init__.py | 2 +- pyclasher/requests/ClanSearch.py | 6 +- pyclasher/requests/ClanSearch.pyi | 4 +- .../request_tests/test_clan.py | 195 ------------------ tests/asyncronous_tests/test_client.py | 54 ----- tests/constants.py | 40 +--- .../request_tests => requests}/__init__.py | 0 .../async_tests}/__init__.py | 0 tests/requests/async_tests/conftest.py | 30 +++ tests/requests/async_tests/test_clan.py | 192 +++++++++++++++++ .../request_tests/__init__.py | 0 .../request_tests/test_clan.py | 178 ---------------- tests/synchronous_tests/test_client.py | 43 ---- tests/test_client.py | 34 +++ 18 files changed, 283 insertions(+), 520 deletions(-) delete mode 100644 tests/asyncronous_tests/request_tests/test_clan.py delete mode 100644 tests/asyncronous_tests/test_client.py rename tests/{asyncronous_tests/request_tests => requests}/__init__.py (100%) rename tests/{synchronous_tests => requests/async_tests}/__init__.py (100%) create mode 100644 tests/requests/async_tests/conftest.py create mode 100644 tests/requests/async_tests/test_clan.py delete mode 100644 tests/synchronous_tests/request_tests/__init__.py delete mode 100644 tests/synchronous_tests/request_tests/test_clan.py delete mode 100644 tests/synchronous_tests/test_client.py create mode 100644 tests/test_client.py diff --git a/pyclasher/models/BaseModels.py b/pyclasher/models/BaseModels.py index 62b03eb..0f75fc6 100644 --- a/pyclasher/models/BaseModels.py +++ b/pyclasher/models/BaseModels.py @@ -55,6 +55,11 @@ class IterBaseModel: _main_attribute = None _iter_rtype = Any + def __new__(cls, data): + if data is MISSING: + return MISSING + return super().__new__(cls) + def __init__(self, data): self._data = data if self._data is not None and self._data is not MISSING: diff --git a/pyclasher/models/Clan.pyi b/pyclasher/models/Clan.pyi index db5b2de..c4a991c 100644 --- a/pyclasher/models/Clan.pyi +++ b/pyclasher/models/Clan.pyi @@ -210,7 +210,7 @@ class Clan(BaseClan): ... @property - def war_wins(self) -> int: + def war_wins(self) -> Missing | int: """ clan's war wins @@ -220,7 +220,7 @@ class Clan(BaseClan): ... @property - def war_ties(self) -> int: + def war_ties(self) -> Missing | int: """ clan's war ties @@ -230,7 +230,7 @@ class Clan(BaseClan): ... @property - def war_losses(self) -> int: + def war_losses(self) -> Missing | int: """ clan's war losses @@ -250,7 +250,7 @@ class Clan(BaseClan): ... @property - def chat_language(self) -> Language: + def chat_language(self) -> Missing | Language: """ clan's chat language @@ -290,7 +290,7 @@ class Clan(BaseClan): ... @property - def location(self) -> Location: + def location(self) -> Missing | Location: """ clan's location @@ -320,7 +320,7 @@ class Clan(BaseClan): ... @property - def description(self) -> str: + def description(self) -> Missing | str: """ clan's description @@ -330,7 +330,7 @@ class Clan(BaseClan): ... @property - def clan_capital(self) -> ClanCapital: + def clan_capital(self) -> Missing | ClanCapital: """ clan's capital diff --git a/pyclasher/models/ClanCapitalRaidSeasons.pyi b/pyclasher/models/ClanCapitalRaidSeasons.pyi index c56b9e7..1961b0f 100644 --- a/pyclasher/models/ClanCapitalRaidSeasons.pyi +++ b/pyclasher/models/ClanCapitalRaidSeasons.pyi @@ -1,5 +1,6 @@ from typing import Iterator +from ..Exceptions import Missing from .BaseModels import BaseModel, IterBaseModel, BaseClanMember, BaseClan, Time @@ -526,7 +527,7 @@ class ClanCapitalRaidSeason(BaseModel): ... @property - def members(self) -> ClanCapitalRaidSeasonMemberList: + def members(self) -> Missing | ClanCapitalRaidSeasonMemberList: """ member list diff --git a/pyclasher/models/ClanMember.pyi b/pyclasher/models/ClanMember.pyi index 3ca43e0..529abfe 100644 --- a/pyclasher/models/ClanMember.pyi +++ b/pyclasher/models/ClanMember.pyi @@ -1,3 +1,4 @@ +from ..Exceptions import Missing from .BaseModels import BaseClanMember from .Enums import ClanRole from .Leagues import League, BuilderBaseLeague @@ -110,7 +111,7 @@ class ClanMember(BaseClanMember): ... @property - def player_house(self) -> PlayerHouse: + def player_house(self) -> Missing | PlayerHouse: """ player house diff --git a/pyclasher/models/__init__.py b/pyclasher/models/__init__.py index 973ca21..4f3fb42 100644 --- a/pyclasher/models/__init__.py +++ b/pyclasher/models/__init__.py @@ -24,7 +24,7 @@ from .ClanWarLog import ClanWarLogEntry, ClanWarLog from .Enums import ApiCodes, ClanType, WarFrequency, Locations, Leagues, CapitalLeagues, \ BuilderBaseLeagues, WarLeagues, Labels, Languages, ClanWarState, ClanWarLeagueGroupState, \ - ClanWarResult, WarPreference, PlayerHouseElementType, Village, TokenStatus + ClanWarResult, WarPreference, PlayerHouseElementType, Village, TokenStatus, ClanRole # gold pass season from .GoldPassSeason import GoldPassSeason # labels diff --git a/pyclasher/requests/ClanSearch.py b/pyclasher/requests/ClanSearch.py index 2df93d9..b6890ef 100644 --- a/pyclasher/requests/ClanSearch.py +++ b/pyclasher/requests/ClanSearch.py @@ -11,7 +11,7 @@ class ClanSearchRequest(IterRequestModel): change in the future releases of the API. """ - name = None + clan_name = None _iter_rtype = Clan _list_rtype = ClanList @@ -48,7 +48,7 @@ def __init__(self, name=None, war_frequency=None, location=None, min_members=Non :type before: str """ - self.name = name + self.clan_name = name IterRequestModel.__init__(self, "clans", kwargs={ 'name': name, @@ -60,5 +60,5 @@ def __init__(self, name=None, war_frequency=None, location=None, min_members=Non 'minClanLevel': min_clan_level, 'labelIds': ",".join((label.value.id for label in label_ids)) if label_ids is not None else None, 'limit': limit, 'after': after, 'before': before}) - self._main_attribute = self.name + self._main_attribute = self.clan_name return diff --git a/pyclasher/requests/ClanSearch.pyi b/pyclasher/requests/ClanSearch.pyi index 82ea086..56f412a 100644 --- a/pyclasher/requests/ClanSearch.pyi +++ b/pyclasher/requests/ClanSearch.pyi @@ -5,14 +5,14 @@ from ..models import ClanList, WarFrequency, Locations, Labels, Clan class ClanSearchRequest(IterRequestModel): - name: str = None + clan_name: str = None _iter_rtype = Clan _list_rtype = ClanList def __init__(self, name: str = None, war_frequency: WarFrequency = None, location: Locations = None, min_members: int = None, max_members: int = None, min_clan_points: int = None, min_clan_level: int = None, label_ids: list[Labels] = None, limit: int = None, after: str = None, before: str = None) -> None: - self.name = name + self.clan_name = name ... @property diff --git a/tests/asyncronous_tests/request_tests/test_clan.py b/tests/asyncronous_tests/request_tests/test_clan.py deleted file mode 100644 index 52ec491..0000000 --- a/tests/asyncronous_tests/request_tests/test_clan.py +++ /dev/null @@ -1,195 +0,0 @@ -from asyncio import run -from pyclasher import RequestNotDone, ClanWarLogRequest, ClanSearchRequest, ClanCurrentWarRequest, ClanRequest, \ - ClanMembersRequest, ClanCapitalRaidSeasonsRequest -from pyclasher.models import ClanWarLog, Paging, ClanMemberList, Location, WarLeague, CapitalLeague, WarFrequency, \ - ClanType, BadgeUrls, Language, ClanCapital, LabelList, WarClan, ClanWarState, Time, ClanList, ClanCapitalRaidSeasons -from ...constants import my_test_clan_tag, client, my_test_clan_name - - -def test_clans_currentwar_leaguegroup(): - """ - request for this test is not implemented yet - """ - ... - - -def test_clanwarleagues_wars(): - """ - request for this test is not implemented yet - """ - ... - - -def test_clans_warlog() -> None: - async def async_test() -> None: - async with client: - clan_war_log = ClanWarLogRequest(my_test_clan_tag) - - try: - clan_war_log_items = clan_war_log.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan_war_log.request() - - assert isinstance(clan_war_log.to_dict(), dict) - assert 0 <= len(clan_war_log) - assert isinstance(clan_war_log.items, ClanWarLog) - assert isinstance(clan_war_log.paging, Paging) - - return - - return run(async_test()) - - -def test_clans_search() -> None: - async def async_test() -> None: - async with client: - clan_search = ClanSearchRequest(my_test_clan_name, limit=10) - - try: - clan_search_items = clan_search.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan_search.request() - - assert isinstance(clan_search.to_dict(), dict) - assert 0 <= len(clan_search) <= 10 - assert isinstance(clan_search.items, ClanList) - assert isinstance(clan_search.paging, Paging) - - return - - return run(async_test()) - - -def test_clans_currentwar() -> None: - async def async_test() -> None: - async with client: - clan_current_war = ClanCurrentWarRequest(my_test_clan_tag) - - try: - clan_current_war_state = clan_current_war.state - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan_current_war.request() - - assert isinstance(clan_current_war.to_dict(), dict) - assert isinstance(clan_current_war.clan, WarClan) - assert isinstance(clan_current_war.state, ClanWarState) - if clan_current_war.state != ClanWarState.NOT_IN_WAR: - assert isinstance(clan_current_war.team_size, int) - assert isinstance(clan_current_war.attacks_per_member, int) - assert isinstance(clan_current_war.opponent, WarClan) - assert isinstance(clan_current_war.end_time, Time) - assert isinstance(clan_current_war.start_time, Time) - assert isinstance(clan_current_war.preparation_start_time, Time) - - return - - return run(async_test()) - - -def test_clans_clan() -> None: - async def async_test() -> None: - async with client: - clan = ClanRequest(my_test_clan_tag) - - try: - clan_tag = clan.tag - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan.request() - - assert isinstance(clan.to_dict(), dict) - assert isinstance(clan.tag, str) - assert isinstance(clan.required_trophies, int) - assert isinstance(clan.name, str) - assert isinstance(clan.members, int) - assert isinstance(clan.member_list, ClanMemberList) - assert isinstance(clan.location, Location) - assert isinstance(clan.war_league, WarLeague) - assert isinstance(clan.capital_league, CapitalLeague) - assert isinstance(clan.war_frequency, WarFrequency) - assert isinstance(clan.type, ClanType) - assert isinstance(clan.description, str) - assert isinstance(clan.war_wins, int) - assert isinstance(clan.clan_level, int) - assert isinstance(clan.badge_urls, BadgeUrls) - assert isinstance(clan.chat_language, Language) - assert isinstance(clan.clan_builder_base_points, int) - assert isinstance(clan.clan_capital, ClanCapital) - assert isinstance(clan.clan_capital_points, int) - assert isinstance(clan.clan_points, int) - assert isinstance(clan.is_family_friendly, bool) - assert isinstance(clan.is_war_log_public, bool) - assert isinstance(clan.labels, LabelList) - assert isinstance(clan.required_builder_base_trophies, int) - assert isinstance(clan.required_townhall_level, int) - assert isinstance(clan.war_losses, int) - assert isinstance(clan.war_ties, int) - assert isinstance(clan.war_win_streak, int) - assert clan.clan_tag == clan.tag - - return - - return run(async_test()) - - -def test_clans_members() -> None: - async def async_test() -> None: - async with client: - clan_members = ClanMembersRequest(my_test_clan_tag) - - try: - clan_members_items = clan_members.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan_members.request() - - assert isinstance(clan_members.to_dict(), dict) - assert 0 <= len(clan_members) <= 50 - assert isinstance(clan_members.items, ClanMemberList) - assert isinstance(clan_members.paging, Paging) - - return - - return run(async_test()) - - -def test_clans_capitalraidseasons(): - async def async_test(): - async with client: - clan_capital_raid_seasons = ClanCapitalRaidSeasonsRequest(my_test_clan_tag) - - try: - clan_capital_raid_seasons_items = clan_capital_raid_seasons.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - await clan_capital_raid_seasons.request() - - assert isinstance(clan_capital_raid_seasons.to_dict(), dict) - assert 0 <= len(clan_capital_raid_seasons) - assert isinstance(clan_capital_raid_seasons.items, ClanCapitalRaidSeasons) - assert isinstance(clan_capital_raid_seasons.paging, Paging) - - return - - return run(async_test()) diff --git a/tests/asyncronous_tests/test_client.py b/tests/asyncronous_tests/test_client.py deleted file mode 100644 index b2c34a2..0000000 --- a/tests/asyncronous_tests/test_client.py +++ /dev/null @@ -1,54 +0,0 @@ -from os import environ -from asyncio import run, Queue -from pyclasher import PyClasherClient - - -login_email = environ.get("CLASH_OF_CLANS_LOGIN_EMAIL") -login_password = environ.get("CLASH_OF_CLANS_LOGIN_PASSWORD") - - -def test_initialise_client() -> None: - async def __async_test_function() -> None: - client = await PyClasherClient.from_login(login_email, login_password, login_count=1) - - assert client.initialised - - assert isinstance(client.queue, Queue) - return - - run(__async_test_function()) - return - - -def test_initialise_client_multiple_tokens(): - async def __async_test_function() -> None: - client = await PyClasherClient.from_login(login_email, login_password, login_count=5) - - assert client.initialised - - assert isinstance(client.queue, Queue) - return - - run(__async_test_function()) - return - - -def test_start_close() -> None: - async def __async_test_function() -> None: - client = await PyClasherClient.from_login(login_email, login_password, login_count=1) - - assert not client.is_running - client.start() - assert client.is_running - await client.close() - - assert not client.is_running - - async with client: - assert client.is_running - assert not client.is_running - - return - - run(__async_test_function()) - return diff --git a/tests/constants.py b/tests/constants.py index a6cfeff..e682db9 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,42 +1,12 @@ from os import environ -from pyclasher import PyClasherClient +CLASH_OF_CLANS_LOGIN_EMAIL = environ.get("CLASH_OF_CLANS_LOGIN_EMAIL") -class Invalid(Exception): - def __init__(self, reason: str) -> None: - super().__init__() - self.reason = reason - return +CLASH_OF_CLANS_LOGIN_PASSWORD = environ.get("CLASH_OF_CLANS_LOGIN_PASSWORD") - def __str__(self) -> str: - return self.reason +TEST_CLAN_TAG = environ.get("TEST_CLAN_TAG") +TEST_PLAYER_TAG = environ.get("TEST_PLAYER_TAG") -if "CLASH_OF_CLANS_LOGIN_EMAIL" in environ.keys(): - login_email = environ.get("CLASH_OF_CLANS_LOGIN_EMAIL") -else: - raise Invalid("The environment does not provide 'CLASH_OF_CLANS_LOGIN_EMAIL'.") - -if "CLASH_OF_CLANS_LOGIN_PASSWORD" in environ.keys(): - login_password = environ.get("CLASH_OF_CLANS_LOGIN_PASSWORD") -else: - raise Invalid("The environment does not provide 'CLASH_OF_CLANS_LOGIN_PASSWORD'.") - -if "TEST_CLAN_TAG" in environ.keys(): - my_test_clan_tag = environ.get("TEST_CLAN_TAG") -else: - raise Invalid("The environment does not provide 'TEST_CLAN_TAG'.") - -if "TEST_PLAYER_TAG" in environ.keys(): - my_test_player_tag = environ.get("TEST_PLAYER_TAG") -else: - raise Invalid("The environment does not provide 'TEST_PLAYER_TAG'.") - -if "TEST_CLAN_NAME" in environ.keys(): - my_test_clan_name = environ.get("TEST_CLAN_NAME") -else: - raise Invalid("The environment does not provide 'TEST_CLAN_NAME'.") - - -client = PyClasherClient.from_login(login_email, login_password, requests_per_second=5, login_count=5) +TEST_CLAN_NAME = environ.get("TEST_CLAN_NAME") diff --git a/tests/asyncronous_tests/request_tests/__init__.py b/tests/requests/__init__.py similarity index 100% rename from tests/asyncronous_tests/request_tests/__init__.py rename to tests/requests/__init__.py diff --git a/tests/synchronous_tests/__init__.py b/tests/requests/async_tests/__init__.py similarity index 100% rename from tests/synchronous_tests/__init__.py rename to tests/requests/async_tests/__init__.py diff --git a/tests/requests/async_tests/conftest.py b/tests/requests/async_tests/conftest.py new file mode 100644 index 0000000..63675f3 --- /dev/null +++ b/tests/requests/async_tests/conftest.py @@ -0,0 +1,30 @@ +import pytest +import pytest_asyncio +from asyncio import new_event_loop + +from pyclasher import PyClasherClient + +from constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD + + +@pytest.fixture(scope="package") +def event_loop(): + print("Setting event loop ...") + loop = new_event_loop() + + yield loop + + loop.close() + print("Closed event loop.") + + +@pytest_asyncio.fixture(scope="package") +async def pyclasher_client(event_loop): + print("Setting PyClasherClient ...") + client = await PyClasherClient.from_login(CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD) + client.start() + + yield client + + await client.close() + print("\nClosed PyClasherClient.") diff --git a/tests/requests/async_tests/test_clan.py b/tests/requests/async_tests/test_clan.py new file mode 100644 index 0000000..de45e92 --- /dev/null +++ b/tests/requests/async_tests/test_clan.py @@ -0,0 +1,192 @@ +import pytest + +from pyclasher import (ClanRequest, ClanMembersRequest, Missing, ClanCurrentWarRequest, ClanWarLogRequest, + ClanSearchRequest, ClanCapitalRaidSeasonsRequest) +from pyclasher.models import (ClanType, WarFrequency, BadgeUrls, WarLeague, CapitalLeague, Language, ClanCapital, + LabelList, Location, ClanMemberList, Paging, BuilderBaseLeague, League, PlayerHouse, + ClanRole, ClanMember, ClanWarState, WarClan, Time, ClanWarLog, ClanWarResult, ClanList, + ClanCapitalRaidSeasons, ClanCapitalRaidSeasonMemberList, + ClanCapitalRaidSeasonAttackLogList, ClanCapitalRaidSeasonDefenseLogList) + +from ...constants import TEST_CLAN_TAG, TEST_CLAN_NAME + + +@pytest.mark.asyncio +async def test_clan(event_loop, pyclasher_client): + clan = ClanRequest(TEST_CLAN_TAG) + + await clan.request() + + assert isinstance(clan.to_dict(), dict) + assert clan.tag == TEST_CLAN_TAG + assert clan.clan_tag == TEST_CLAN_TAG + assert isinstance(clan.clan_level, int) + assert isinstance(clan.name, str) + assert isinstance(clan.type, ClanType) + assert isinstance(clan.war_ties, int) + assert isinstance(clan.war_losses, int) + assert isinstance(clan.war_wins, int) + assert isinstance(clan.war_win_streak, int) + assert isinstance(clan.war_frequency, WarFrequency) + assert isinstance(clan.badge_urls, BadgeUrls) + assert isinstance(clan.war_league, WarLeague) + assert isinstance(clan.capital_league, CapitalLeague) + assert isinstance(clan.chat_language, Language) + assert isinstance(clan.clan_builder_base_points, int) + assert isinstance(clan.clan_capital, ClanCapital) + assert isinstance(clan.clan_capital_points, int) + assert isinstance(clan.clan_points, int) + assert isinstance(clan.description, str) + assert isinstance(clan.is_family_friendly, bool) + assert isinstance(clan.is_war_log_public, bool) + assert isinstance(clan.labels, LabelList) + assert isinstance(clan.location, Location) + assert isinstance(clan.member_list, ClanMemberList) + assert isinstance(clan.members, int) + assert 0 <= clan.members <= 50 + assert isinstance(clan.required_builder_base_trophies, int) + assert isinstance(clan.required_trophies, int) + assert isinstance(clan.required_townhall_level, int) + assert 1 <= clan.required_townhall_level <= 15 + + +@pytest.mark.asyncio +async def test_clan_member(event_loop, pyclasher_client): + clan_members = ClanMembersRequest(TEST_CLAN_TAG) + + await clan_members.request() + + assert isinstance(clan_members.to_dict(), dict) + assert clan_members.clan_tag == TEST_CLAN_TAG + assert isinstance(clan_members.items, ClanMemberList) + assert isinstance(clan_members.paging, Paging) + + for member in clan_members: + assert isinstance(member, ClanMember) + assert isinstance(member.to_dict(), dict) + assert isinstance(member.tag, str) + assert isinstance(member.name, str) + assert isinstance(member.clan_rank, int) + assert 1 <= member.clan_rank <= 50 + assert isinstance(member.trophies, int) + assert isinstance(member.builder_base_league, BuilderBaseLeague) + assert isinstance(member.builder_base_trophies, int) + assert isinstance(member.donations, int) + assert isinstance(member.donations_received, int) + assert isinstance(member.exp_level, int) + assert isinstance(member.league, League) + assert isinstance(member.player_house, (PlayerHouse, Missing)) + assert isinstance(member.previous_clan_rank, int) + assert 1 <= member.previous_clan_rank <= 50 + assert isinstance(member.role, ClanRole) + + +@pytest.mark.asyncio +async def test_clan_current_war(event_loop, pyclasher_client): + current_war = ClanCurrentWarRequest(TEST_CLAN_TAG) + + await current_war.request() + + assert isinstance(current_war.to_dict(), dict) + assert current_war.clan_tag == TEST_CLAN_TAG + assert isinstance(current_war.state, ClanWarState) + assert isinstance(current_war.clan, WarClan) + assert isinstance(current_war.opponent, WarClan) + + if current_war.state == ClanWarState.IN_WAR or current_war.state == ClanWarState.PREPARATION: + assert isinstance(current_war.attacks_per_member, int) + assert isinstance(current_war.end_time, Time) + assert isinstance(current_war.team_size, int) + assert isinstance(current_war.preparation_start_time, Time) + assert isinstance(current_war.start_time, Time) + + +@pytest.mark.asyncio +async def test_clan_war_log(event_loop, pyclasher_client): + war_log = ClanWarLogRequest(TEST_CLAN_TAG) + + await war_log.request() + + assert isinstance(war_log.to_dict(), dict) + assert war_log.clan_tag == TEST_CLAN_TAG + assert isinstance(war_log.items, ClanWarLog) + assert isinstance(war_log.paging, Paging) + + for war in war_log: + assert isinstance(war.attacks_per_member, int) + assert war.attacks_per_member == 1 or war.attacks_per_member == 2 + assert isinstance(war.clan, WarClan) + assert isinstance(war.opponent, WarClan) + assert isinstance(war.end_time, Time) + assert isinstance(war.team_size, int) + assert 5 <= war.team_size <= 50 + assert isinstance(war.result, ClanWarResult) + + +@pytest.mark.asyncio +async def test_clan_search(event_loop, pyclasher_client): + clans = ClanSearchRequest(TEST_CLAN_NAME) + + await clans.request() + + assert isinstance(clans.to_dict(), dict) + assert clans.clan_name == TEST_CLAN_NAME + assert isinstance(clans.items, ClanList) + assert isinstance(clans.paging, Paging) + + for clan in clans: + assert isinstance(clan.to_dict(), dict) + assert isinstance(clan.clan_level, int) + assert isinstance(clan.name, str) + assert isinstance(clan.type, ClanType) + assert isinstance(clan.war_ties, (int, Missing)) + assert isinstance(clan.war_losses, (int, Missing)) + assert isinstance(clan.war_wins, (int, Missing)) + assert isinstance(clan.war_win_streak, int) + assert isinstance(clan.war_frequency, WarFrequency) + assert isinstance(clan.badge_urls, BadgeUrls) + assert isinstance(clan.war_league, WarLeague) + assert isinstance(clan.capital_league, CapitalLeague) + assert isinstance(clan.chat_language, (Language, Missing)) + assert isinstance(clan.clan_builder_base_points, int) + assert isinstance(clan.clan_capital, (ClanCapital, Missing)) + assert isinstance(clan.clan_capital_points, int) + assert isinstance(clan.clan_points, int) + assert isinstance(clan.description, (str, Missing)) + assert isinstance(clan.is_family_friendly, bool) + assert isinstance(clan.is_war_log_public, bool) + assert isinstance(clan.labels, LabelList) + assert isinstance(clan.location, (Location, Missing)) + assert isinstance(clan.member_list, Missing) + assert isinstance(clan.members, int) + assert 0 <= clan.members <= 50 + assert isinstance(clan.required_builder_base_trophies, int) + assert isinstance(clan.required_trophies, int) + assert isinstance(clan.required_townhall_level, int) + assert 1 <= clan.required_townhall_level <= 15 + + +@pytest.mark.asyncio +async def test_clan_capital_raid_season(event_loop, pyclasher_client): + raid_seasons = ClanCapitalRaidSeasonsRequest(TEST_CLAN_TAG) + + await raid_seasons.request() + + assert isinstance(raid_seasons.to_dict(), dict) + assert raid_seasons.clan_tag == TEST_CLAN_TAG + assert isinstance(raid_seasons.items, ClanCapitalRaidSeasons) + assert isinstance(raid_seasons.paging, Paging) + + for rs in raid_seasons: + assert isinstance(rs.to_dict(), dict) + assert isinstance(rs.state, str) + assert isinstance(rs.members, (ClanCapitalRaidSeasonMemberList, Missing)) + assert isinstance(rs.end_time, Time) + assert isinstance(rs.start_time, Time) + assert isinstance(rs.attack_log, ClanCapitalRaidSeasonAttackLogList) + assert isinstance(rs.capital_total_loot, int) + assert isinstance(rs.defense_log, ClanCapitalRaidSeasonDefenseLogList) + assert isinstance(rs.defensive_reward, int) + assert isinstance(rs.enemy_districts_destroyed, int) + assert isinstance(rs.raids_completed, int) + assert isinstance(rs.total_attacks, int) diff --git a/tests/synchronous_tests/request_tests/__init__.py b/tests/synchronous_tests/request_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/synchronous_tests/request_tests/test_clan.py b/tests/synchronous_tests/request_tests/test_clan.py deleted file mode 100644 index f215cca..0000000 --- a/tests/synchronous_tests/request_tests/test_clan.py +++ /dev/null @@ -1,178 +0,0 @@ -from pyclasher import RequestNotDone, ClanWarLogRequest, ClanSearchRequest, ClanCurrentWarRequest, ClanRequest, \ - ClanMembersRequest, ClanCapitalRaidSeasonsRequest -from pyclasher.models import ClanWarLog, Paging, ClanMemberList, Location, WarLeague, CapitalLeague, WarFrequency, \ - ClanType, BadgeUrls, Language, ClanCapital, LabelList, WarClan, ClanWarState, Time, ClanList, ClanCapitalRaidSeasons -from ...constants import my_test_clan_tag, client, my_test_clan_name - - -def test_clans_currentwar_leaguegroup(): - """ - request for this test is not implemented yet - """ - client.reset_client(reset_tokens=False, reset_loop=False) - - ... - - -def test_clanwarleagues_wars(): - """ - request for this test is not implemented yet - """ - ... - - -def test_clans_warlog() -> None: - with client: - clan_war_log = ClanWarLogRequest(my_test_clan_tag) - - try: - clan_war_log_items = clan_war_log.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan_war_log.request() - - assert isinstance(clan_war_log.to_dict(), dict) - assert 0 <= len(clan_war_log) - assert isinstance(clan_war_log.items, ClanWarLog) - assert isinstance(clan_war_log.paging, Paging) - - return - - -def test_clans_search() -> None: - with client: - clan_search = ClanSearchRequest(my_test_clan_name, limit=10) - - try: - clan_search_items = clan_search.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan_search.request() - - assert isinstance(clan_search.to_dict(), dict) - assert 0 <= len(clan_search) <= 10 - assert isinstance(clan_search.items, ClanList) - assert isinstance(clan_search.paging, Paging) - - return - - -def test_clans_currentwar() -> None: - with client: - clan_current_war = ClanCurrentWarRequest(my_test_clan_tag) - - try: - clan_current_war_state = clan_current_war.state - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan_current_war.request() - - assert isinstance(clan_current_war.to_dict(), dict) - assert isinstance(clan_current_war.clan, WarClan) - assert isinstance(clan_current_war.state, ClanWarState) - if clan_current_war.state != ClanWarState.NOT_IN_WAR: - assert isinstance(clan_current_war.team_size, int) - assert isinstance(clan_current_war.attacks_per_member, int) - assert isinstance(clan_current_war.opponent, WarClan) - assert isinstance(clan_current_war.end_time, Time) - assert isinstance(clan_current_war.start_time, Time) - assert isinstance(clan_current_war.preparation_start_time, Time) - - return - - -def test_clans_clan() -> None: - with client: - clan = ClanRequest(my_test_clan_tag) - - try: - clan_tag = clan.tag - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan.request() - - assert isinstance(clan.to_dict(), dict) - assert isinstance(clan.tag, str) - assert isinstance(clan.required_trophies, int) - assert isinstance(clan.name, str) - assert isinstance(clan.members, int) - assert isinstance(clan.member_list, ClanMemberList) - assert isinstance(clan.location, Location) - assert isinstance(clan.war_league, WarLeague) - assert isinstance(clan.capital_league, CapitalLeague) - assert isinstance(clan.war_frequency, WarFrequency) - assert isinstance(clan.type, ClanType) - assert isinstance(clan.description, str) - assert isinstance(clan.war_wins, int) - assert isinstance(clan.clan_level, int) - assert isinstance(clan.badge_urls, BadgeUrls) - assert isinstance(clan.chat_language, Language) - assert isinstance(clan.clan_builder_base_points, int) - assert isinstance(clan.clan_capital, ClanCapital) - assert isinstance(clan.clan_capital_points, int) - assert isinstance(clan.clan_points, int) - assert isinstance(clan.is_family_friendly, bool) - assert isinstance(clan.is_war_log_public, bool) - assert isinstance(clan.labels, LabelList) - assert isinstance(clan.required_builder_base_trophies, int) - assert isinstance(clan.required_townhall_level, int) - assert isinstance(clan.war_losses, int) - assert isinstance(clan.war_ties, int) - assert isinstance(clan.war_win_streak, int) - assert clan.clan_tag == clan.tag - - return - - -def test_clans_members() -> None: - with client: - clan_members = ClanMembersRequest(my_test_clan_tag) - - try: - clan_members_items = clan_members.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan_members.request() - - assert isinstance(clan_members.to_dict(), dict) - assert 0 <= len(clan_members) <= 50 - assert isinstance(clan_members.items, ClanMemberList) - assert isinstance(clan_members.paging, Paging) - - return - - -def test_clans_capitalraidseasons(): - with client: - clan_capital_raid_seasons = ClanCapitalRaidSeasonsRequest(my_test_clan_tag) - - try: - clan_capital_raid_seasons_items = clan_capital_raid_seasons.items - except Exception as exception: - assert isinstance(exception, RequestNotDone) - else: - assert False - - clan_capital_raid_seasons.request() - - assert isinstance(clan_capital_raid_seasons.to_dict(), dict) - assert 0 <= len(clan_capital_raid_seasons) - assert isinstance(clan_capital_raid_seasons.items, ClanCapitalRaidSeasons) - assert isinstance(clan_capital_raid_seasons.paging, Paging) - - return diff --git a/tests/synchronous_tests/test_client.py b/tests/synchronous_tests/test_client.py deleted file mode 100644 index 5e7939b..0000000 --- a/tests/synchronous_tests/test_client.py +++ /dev/null @@ -1,43 +0,0 @@ -from os import environ -from asyncio import Queue -from pyclasher import PyClasherClient - -login_email = environ.get("CLASH_OF_CLANS_LOGIN_EMAIL") -login_password = environ.get("CLASH_OF_CLANS_LOGIN_PASSWORD") - - -def test_initialise_client() -> None: - client = PyClasherClient.from_login(login_email, login_password, login_count=1) - - assert client.initialised - - assert isinstance(client.queue, Queue) - - return - - -def test_initialise_client_multiple_tokens(): - client = PyClasherClient.from_login(login_email, login_password, login_count=5) - - assert client.initialised - - assert isinstance(client.queue, Queue) - - return - - -def test_start_close() -> None: - client = PyClasherClient.from_login(login_email, login_password, login_count=1) - - assert not client.is_running - client.start() - assert client.is_running - client.close() - - assert not client.is_running - - with client: - assert client.is_running - assert not client.is_running - - return diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..37ff5b2 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,34 @@ +import pytest + +from asyncio import Queue, AbstractEventLoop + +from pyclasher import PyClasherClient + +from constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD + + +@pytest.mark.asyncio +async def test_client(): + assert not PyClasherClient.initialised + + client = await PyClasherClient.from_login(CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD) + + assert PyClasherClient.initialised + assert not client.is_running + assert isinstance(client.queue, Queue) + + client.start() + + assert client.is_running + assert isinstance(client.loop, AbstractEventLoop) + + await client.close() + + assert not client.is_running + assert isinstance(client.queue, Queue) + + async with client: + assert client.is_running + assert isinstance(client.loop, AbstractEventLoop) + + assert not client.is_running From 6cc970bc38fa29e62675edd2fd219a551afba24e Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:50:01 +0200 Subject: [PATCH 11/26] fix!: removed `Player.best_versus_trophies` it will become depreciated, use `Player.best_builder_base_trophies` instead --- pyclasher/models/Player.py | 4 ---- pyclasher/models/Player.pyi | 4 ---- 2 files changed, 8 deletions(-) diff --git a/pyclasher/models/Player.py b/pyclasher/models/Player.py index 1fd6d10..21112ab 100644 --- a/pyclasher/models/Player.py +++ b/pyclasher/models/Player.py @@ -158,10 +158,6 @@ def defense_wins(self): def versus_trophies(self): return self._get_data('versusTrophies') - @property - def best_versus_trophies(self): - return self._get_data('bestVersusTrophies') - @property def town_hall_level(self): return self._get_data('townHallLevel') diff --git a/pyclasher/models/Player.pyi b/pyclasher/models/Player.pyi index ac084b0..e31072c 100644 --- a/pyclasher/models/Player.pyi +++ b/pyclasher/models/Player.pyi @@ -206,10 +206,6 @@ class Player(BaseModel): def versus_trophies(self) -> int: ... - @property - def best_versus_trophies(self) -> int: - ... - @property def town_hall_level(self) -> int: ... From 7fa449a576df4847028c6f6cb6d401e56608e8e1 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:57:12 +0200 Subject: [PATCH 12/26] fix!: removed `Player.versus_trophies` it will become depreciated, use `Player.builder_base_trophies` instead --- pyclasher/models/Player.py | 4 ---- pyclasher/models/Player.pyi | 4 ---- 2 files changed, 8 deletions(-) diff --git a/pyclasher/models/Player.py b/pyclasher/models/Player.py index 21112ab..66197ff 100644 --- a/pyclasher/models/Player.py +++ b/pyclasher/models/Player.py @@ -154,10 +154,6 @@ def attack_wins(self): def defense_wins(self): return self._get_data('defenseWins') - @property - def versus_trophies(self): - return self._get_data('versusTrophies') - @property def town_hall_level(self): return self._get_data('townHallLevel') diff --git a/pyclasher/models/Player.pyi b/pyclasher/models/Player.pyi index e31072c..4bad71b 100644 --- a/pyclasher/models/Player.pyi +++ b/pyclasher/models/Player.pyi @@ -202,10 +202,6 @@ class Player(BaseModel): def defense_wins(self) -> int: ... - @property - def versus_trophies(self) -> int: - ... - @property def town_hall_level(self) -> int: ... From cbf9ad8a7dc727160c062b2fff01518fbd6d1d81 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 16:58:01 +0200 Subject: [PATCH 13/26] refactor: used double equality instead of 2 lines --- tests/requests/async_tests/test_clan.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/requests/async_tests/test_clan.py b/tests/requests/async_tests/test_clan.py index de45e92..13e8947 100644 --- a/tests/requests/async_tests/test_clan.py +++ b/tests/requests/async_tests/test_clan.py @@ -18,8 +18,7 @@ async def test_clan(event_loop, pyclasher_client): await clan.request() assert isinstance(clan.to_dict(), dict) - assert clan.tag == TEST_CLAN_TAG - assert clan.clan_tag == TEST_CLAN_TAG + assert clan.tag == clan.clan_tag == TEST_CLAN_TAG assert isinstance(clan.clan_level, int) assert isinstance(clan.name, str) assert isinstance(clan.type, ClanType) From d4e92384c64e8298a70195117222ed01b3197e37 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 17:04:23 +0200 Subject: [PATCH 14/26] fix: `Player.league` can be MISSING --- pyclasher/models/Player.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyclasher/models/Player.pyi b/pyclasher/models/Player.pyi index 4bad71b..0530def 100644 --- a/pyclasher/models/Player.pyi +++ b/pyclasher/models/Player.pyi @@ -1,5 +1,6 @@ from typing import Iterator +from ..Exceptions import Missing from .BaseModels import BaseModel, IterBaseModel, BaseClan from .Enums import ClanRole, WarPreference, Village from .Labels import LabelList @@ -175,7 +176,7 @@ class PlayerAchievementProgressList(IterBaseModel): class Player(BaseModel): @property - def league(self) -> League: + def league(self) -> Missing | League: ... @property From c6f3dd4b1a5a83891e761ae1c0df395863dbbebc Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 17:07:22 +0200 Subject: [PATCH 15/26] test: added `PlayerRequestTest` --- tests/requests/async_tests/test_player.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/requests/async_tests/test_player.py diff --git a/tests/requests/async_tests/test_player.py b/tests/requests/async_tests/test_player.py new file mode 100644 index 0000000..3097af1 --- /dev/null +++ b/tests/requests/async_tests/test_player.py @@ -0,0 +1,44 @@ +import pytest + +from pyclasher import PlayerRequest, Missing +from pyclasher.models import (ClanRole, PlayerHouse, PlayerClan, PlayerAchievementProgressList, BuilderBaseLeague, + PlayerItemLevelList, LabelList, League, PlayerLegendStatistics, WarPreference) + +from ...constants import TEST_PLAYER_TAG + + +@pytest.mark.asyncio +async def test_player(event_loop, pyclasher_client): + player = PlayerRequest(TEST_PLAYER_TAG) + + await player.request() + + assert isinstance(player.to_dict(), dict) + assert player.player_tag == player.tag == TEST_PLAYER_TAG + assert isinstance(player.role, ClanRole) + assert isinstance(player.clan, PlayerClan) + assert isinstance(player.name, str) + assert isinstance(player.trophies, int) + assert isinstance(player.achievements, PlayerAchievementProgressList) + assert isinstance(player.attack_wins, int) + assert isinstance(player.best_builder_base_trophies, int) + assert isinstance(player.best_trophies, int) + assert isinstance(player.builder_base_league, BuilderBaseLeague) + assert isinstance(player.builder_base_trophies, int) + assert isinstance(player.builder_hall_level, int) + assert isinstance(player.clan_capital_contributions, int) + assert isinstance(player.defense_wins, int) + assert isinstance(player.donations, int) + assert isinstance(player.donations_received, int) + assert isinstance(player.exp_level, int) + assert isinstance(player.heroes, PlayerItemLevelList) + assert isinstance(player.labels, LabelList) + assert isinstance(player.league, (League, Missing)) + assert isinstance(player.legend_statistics, PlayerLegendStatistics) + assert isinstance(player.player_house, PlayerHouse) + assert isinstance(player.spells, PlayerItemLevelList) + assert isinstance(player.town_hall_level, int) + assert isinstance(player.troops, PlayerItemLevelList) + assert isinstance(player.town_hall_weapon_level, int) + assert isinstance(player.versus_battle_wins, int) + assert isinstance(player.war_preference, WarPreference) From 40d815c9fa9e3afa5a2a7a1ba7b542991af48954 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 17:50:13 +0200 Subject: [PATCH 16/26] fix: corrected wrong type hint for `ClanLabelsRequest` and `PlayerLabelsRequest` __init__ method --- pyclasher/requests/ClanLabels.pyi | 3 +++ pyclasher/requests/PlayerLabels.pyi | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pyclasher/requests/ClanLabels.pyi b/pyclasher/requests/ClanLabels.pyi index 54d4c1a..733ea47 100644 --- a/pyclasher/requests/ClanLabels.pyi +++ b/pyclasher/requests/ClanLabels.pyi @@ -8,6 +8,9 @@ class ClanLabelsRequest(IterRequestModel): _iter_rtype = Label _list_rtype = LabelList + def __init__(self) -> None: + ... + @property def items(self) -> _list_rtype: ... diff --git a/pyclasher/requests/PlayerLabels.pyi b/pyclasher/requests/PlayerLabels.pyi index f334de8..26e552e 100644 --- a/pyclasher/requests/PlayerLabels.pyi +++ b/pyclasher/requests/PlayerLabels.pyi @@ -8,6 +8,9 @@ class PlayerLabelsRequest(IterRequestModel): _iter_rtype = Label _list_rtype = LabelList + def __init__(self) -> None: + ... + async def _async_request(self) -> PlayerLabelsRequest: ... From 52d21495e0b5e555be3c877aa235b7880719b8a3 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 17:50:27 +0200 Subject: [PATCH 17/26] test: implemented label tests --- tests/requests/async_tests/test_labels.py | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/requests/async_tests/test_labels.py diff --git a/tests/requests/async_tests/test_labels.py b/tests/requests/async_tests/test_labels.py new file mode 100644 index 0000000..93b18a3 --- /dev/null +++ b/tests/requests/async_tests/test_labels.py @@ -0,0 +1,40 @@ +import pytest + +from pyclasher import ClanLabelsRequest, PlayerLabelsRequest +from pyclasher.models import LabelList, Paging, IconUrls + + +@pytest.mark.asyncio +async def test_clan_labels(event_loop, pyclasher_client): + clan_labels = ClanLabelsRequest() + + await clan_labels.request() + + assert isinstance(clan_labels.to_dict(), dict) + assert isinstance(clan_labels.items, LabelList) + assert isinstance(clan_labels.paging, Paging) + + for label in clan_labels: + assert isinstance(label.to_dict(), dict) + assert isinstance(label.name, str) + assert isinstance(label.id, int) + assert isinstance(label.icon_urls, IconUrls) + + +@pytest.mark.asyncio +async def test_clan_labels(event_loop, pyclasher_client): + player_labels = PlayerLabelsRequest() + + await player_labels.request() + + assert isinstance(player_labels.to_dict(), dict) + assert isinstance(player_labels.items, LabelList) + assert isinstance(player_labels.paging, Paging) + + for label in player_labels: + assert isinstance(label.to_dict(), dict) + assert isinstance(label.name, str) + assert isinstance(label.id, int) + assert isinstance(label.icon_urls, IconUrls) + + From 8ed015951001780043e03e951b0265e91b262780 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 19:46:41 +0200 Subject: [PATCH 18/26] fix: `BaseModel:__init__` default `data` argument is None --- pyclasher/models/BaseModels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyclasher/models/BaseModels.py b/pyclasher/models/BaseModels.py index 0f75fc6..c8458c0 100644 --- a/pyclasher/models/BaseModels.py +++ b/pyclasher/models/BaseModels.py @@ -8,12 +8,12 @@ class BaseModel: _main_attribute = None _data = MISSING - def __new__(cls, data): + def __new__(cls, data=None): if data is MISSING: return MISSING return super().__new__(cls) - def __init__(self, data): + def __init__(self, data=None): if data is not None: self._data = data return From 5f331f04c4078ff14d14b05d4980b10c1bf70552 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 19:47:58 +0200 Subject: [PATCH 19/26] fix: initializing `GoldPassRequest` fix `super()` does not work for multiple inheritance so it got split up --- pyclasher/requests/GoldPass.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyclasher/requests/GoldPass.py b/pyclasher/requests/GoldPass.py index 1bb2dfc..5835c5e 100644 --- a/pyclasher/requests/GoldPass.py +++ b/pyclasher/requests/GoldPass.py @@ -4,6 +4,7 @@ class GoldPassRequest(RequestModel, GoldPassSeason): def __init__(self): - super().__init__("goldpass/seasons/current") + RequestModel.__init__(self, "goldpass/seasons/current") + GoldPassSeason.__init__(self) self._main_attribute = "" return From c5c54c7e9a9e519ae1564eef06ed3fe96a5aef19 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sun, 13 Aug 2023 19:48:51 +0200 Subject: [PATCH 20/26] test: `GoldPassRequest` test --- tests/requests/async_tests/test_goldpass.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/requests/async_tests/test_goldpass.py diff --git a/tests/requests/async_tests/test_goldpass.py b/tests/requests/async_tests/test_goldpass.py new file mode 100644 index 0000000..65b6449 --- /dev/null +++ b/tests/requests/async_tests/test_goldpass.py @@ -0,0 +1,15 @@ +import pytest + +from pyclasher import GoldPassRequest +from pyclasher.models import Time + + +@pytest.mark.asyncio +async def test_goldpass(event_loop, pyclasher_client): + goldpass = GoldPassRequest() + + await goldpass.request() + + assert isinstance(goldpass.to_dict(), dict) + assert isinstance(goldpass.start_time, Time) + assert isinstance(goldpass.end_time, Time) From b608720f98f52bbda93b2175dbbce54321114c6f Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Mon, 14 Aug 2023 17:36:45 +0200 Subject: [PATCH 21/26] test: `LocationsRequest` test --- tests/async_tests/requests/test_locations.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/async_tests/requests/test_locations.py diff --git a/tests/async_tests/requests/test_locations.py b/tests/async_tests/requests/test_locations.py new file mode 100644 index 0000000..050f573 --- /dev/null +++ b/tests/async_tests/requests/test_locations.py @@ -0,0 +1,20 @@ +import pytest + +from pyclasher import LocationsRequest +from pyclasher.models import LocationList, Paging + + +@pytest.mark.asyncio +async def test_locations(event_loop, pyclasher_client): + locations = LocationsRequest() + + await locations.request() + + assert isinstance(locations.to_dict(), dict) + assert isinstance(locations.items, LocationList) + assert isinstance(locations.paging, Paging) + + for location in locations: + assert isinstance(location.to_dict(), dict) + assert isinstance(location.id, int) + assert isinstance(location.name, str) From 17853f5c9d322d2762a9868518c5748e82d7c4c1 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Mon, 14 Aug 2023 17:45:44 +0200 Subject: [PATCH 22/26] fix: `test_locations.py` file was not in the right directory now it is in the right dir and works with the test fixtures --- .../requests => requests/async_tests}/test_locations.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{async_tests/requests => requests/async_tests}/test_locations.py (100%) diff --git a/tests/async_tests/requests/test_locations.py b/tests/requests/async_tests/test_locations.py similarity index 100% rename from tests/async_tests/requests/test_locations.py rename to tests/requests/async_tests/test_locations.py From 10dfd2bfbebe5c39964dd9653b65ea48ffe7d1be Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Wed, 16 Aug 2023 10:16:56 +0200 Subject: [PATCH 23/26] feat: updated README.md included SuperCell fan content policy disclaimer in the README.md file --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4fd91bc..36124e0 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,12 @@ If you find a bug, an error or want custom functionality, please tell me via Discord or open an issue or start a discussion on the GitHub-repository. +--- + +##### Disclaimer +This material is unofficial and is not endorsed by Supercell. For more information see Supercell's Fan Content Policy: +www.supercell.com/fan-content-policy. + From 834c5b56695cbf08d6194be15de03dba285eb95e Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 19 Aug 2023 12:28:25 +0200 Subject: [PATCH 24/26] fix: corrected test workflow the pull_request.yml workflow did not use the requirements_tests.txt file to install the needed requirements --- .github/workflows/pull_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a329d65..662edb3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,8 +21,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install flake8 + pip install -r requirements_tests.txt - name: linting run: | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics From f84e331ee9208b96fe5a70eb579454faa7f2857c Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 19 Aug 2023 12:38:51 +0200 Subject: [PATCH 25/26] fix: solved import error --- tests/requests/async_tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requests/async_tests/conftest.py b/tests/requests/async_tests/conftest.py index 63675f3..f1aaa2d 100644 --- a/tests/requests/async_tests/conftest.py +++ b/tests/requests/async_tests/conftest.py @@ -4,7 +4,7 @@ from pyclasher import PyClasherClient -from constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD +from ...constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD @pytest.fixture(scope="package") From fcf06413c7df1d3804a4afbcbb9b2d83cbb30530 Mon Sep 17 00:00:00 2001 From: 201st-Luka Date: Sat, 19 Aug 2023 12:40:16 +0200 Subject: [PATCH 26/26] fix: solved import error in `test_client.py` --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index 37ff5b2..1df1c59 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,7 +4,7 @@ from pyclasher import PyClasherClient -from constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD +from .constants import CLASH_OF_CLANS_LOGIN_EMAIL, CLASH_OF_CLANS_LOGIN_PASSWORD @pytest.mark.asyncio