Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2.0.2_update'
Browse files Browse the repository at this point in the history
  • Loading branch information
majordoobie committed Aug 28, 2022
2 parents 07f97bd + 563270a commit f8f414b
Show file tree
Hide file tree
Showing 21 changed files with 784 additions and 327 deletions.
11 changes: 5 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ jobs:
# You can use PyPy versions in python-version.
# For example, pypy2 and pypy3
matrix:
python-version: [3.8]
# python-version: ["3.7.13", "3.8.13", "3.9.13", "3.10"]
python-version: [3.8]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# You can test your matrix by printing the current Python version
Expand All @@ -24,7 +25,6 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install discord.py==1.5.0
pip install -r requirements.txt
- name: Running examples as tests
env:
Expand All @@ -35,7 +35,6 @@ jobs:
LINKS_API_PASSWORD: ${{ secrets.LINKS_API_PASSWORD }}
RUNNING_TESTS: true
run: |
python -m examples.discord_bot
python -m examples.discord_links
python -m examples.events
python -m examples.events_example
python -m examples.war_logs
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Byte-compiled / optimized / DLL files
.idea/
__pycache__/
*.py[cod]
*$py.class
Expand Down Expand Up @@ -82,11 +83,11 @@ celerybeat-schedule
*.sage.py

# Environments
.env
examples/.env
.venv
env/
examples/.env/
venv/
ENV/
examples/.env/
env.bak/
venv.bak/

Expand All @@ -107,4 +108,4 @@ venv.bak/
examples/creds.py

# vscode
.vscode/
.vscode/
77 changes: 59 additions & 18 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Getting Started

Installing
-----------
**Python 3.5 or higher is required**
**Python 3.7 or higher is required**

.. code:: sh
Expand All @@ -47,26 +47,39 @@ This example will get a player with a certain tag, and search for 5 clans with a

.. code:: py
import asyncio
import coc
client = coc.login('email', 'password')
async def main():
coc_client = coc.Client()
try:
await coc_client.login("email", "password")
except coc.invalidcredentials as error:
exit(error)
player = await client.get_player("tag")
print("{0.name} has {0.trophies} trophies!".format(player))
print(f"{player.name} has {player.trophies} trophies!")
clans = await client.search_clans(name="Best Clan Ever", limit=5)
clans = await client.search_clans(name="best clan ever", limit=5)
for clan in clans:
print("{0.name} ({0.tag}) has {0.member_count} members".format(clan))
print(f"{clan.name} ({clan.tag}) has {clan.member_count} members")
try:
war = await client.get_current_war("#clantag")
print("{0.clan_tag} is currently in {0.state} state.".format(war))
except coc.PrivateWarLog:
print("Uh oh, they have a private war log!")
print(f"{war.clan_tag} is currently in {war.state} state.")
except coc.privatewarlog:
print("uh oh, they have a private war log!")
client.loop.run_until_complete(main())
client.close()
# make sure to close the session or you will get asyncio
# task pending errors
await client.close()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
Basic Events Example
---------------------
Expand All @@ -75,24 +88,52 @@ whenever someone joins the clan or a member of the clan donates troops.

.. code:: py
import asyncio
import logging
import coc
client = coc.login('email', 'password', client=coc.EventsClient)
@client.event
@coc.ClanEvents.member_join(tags=["#clantag", "#clantag2"])
@coc.ClanEvents.member_join()
async def foo(player, clan):
print("{0.name} ({0.tag}) just joined {1.name} ({1.tag})!".format(player, clan))
print(f"{player.name} ({player.tag}) just joined {clan.name} ({clan.tag})")
@client.event
@coc.ClanEvents.member_donations(tags=["#clantag", "#clantag2"])
@coc.ClanEvents.member_donations()
async def bar(old_member, member):
troops_donated = member.donations - old_member.donations
print("{0} just donated {1} troops!".format(member.name, troops_donated))
print(f"{member.name} just donated {troops_donated} troops!")
client.run_forever()
async def main():
coc_client = coc.EVentsClient()
try:
await coc.login("email", "password")
except coc.InvalidCredentials as error:
exit(error)
# Register all the clans you want to monitor
list_of_clan_tags = ["tag1", "tag2", "tag3"]
coc_client.add_clan_updates(*list_of_clan_tags)
# Register the callbacks for each of the events you are monitoring
coc_client.add_events(
foo,
bar
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
log = logging.getLogger()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
loop.run_forever()
except KeyboardInterrupt:
pass
For more examples see the examples directory

Contributing
Expand Down
2 changes: 1 addition & 1 deletion coc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
SOFTWARE.
"""

__version__ = "2.0.1"
__version__ = "2.1.0"

from .abc import BasePlayer, BaseClan
from .clans import RankedClan, Clan
Expand Down
6 changes: 5 additions & 1 deletion coc/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ def get_detailed_members(self, cls: Type["Player"] = None, load_game_data: bool
if load_game_data and not isinstance(load_game_data, bool):
raise TypeError("load_game_data must be either True or False.")

return PlayerIterator(self._client, (p.tag for p in self.members), cls=cls, load_game_data=load_game_data)
return PlayerIterator(self._client,
(p.tag for p in self.members),
cls=cls,
load_game_data=load_game_data,
members=self.members_dict)


class BasePlayer:
Expand Down
38 changes: 31 additions & 7 deletions coc/clans.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


from .players import ClanMember
from .miscmodels import try_enum, ChatLanguage, Location, Label, WarLeague
from .miscmodels import try_enum, ChatLanguage, Location, Label, WarLeague, CapitalDistrict
from .utils import get, cached_property, correct_tag
from .abc import BaseClan

Expand Down Expand Up @@ -129,6 +129,9 @@ class Clan(BaseClan):
member_cls: :class:`coc.ClanMember`
The type which the members found in :attr:`Clan.members` will be of.
Ensure any overriding of this inherits from :class:`coc.ClanMember`.
capital_district_cls: :class:`coc.CapitalDistrict`
The type which the clan capital districts found in :attr:`Clan.capital_districts` will be of.
Ensure any overriding of this inherits from :class:`coc.CapitalDistrict`.
war_league: :class:`coc.WarLeague`
The clan's CWL league.
"""
Expand All @@ -149,24 +152,28 @@ class Clan(BaseClan):
"member_count",
"_labels",
"_members",
"_districts",
"_client",
"label_cls",
"member_cls",
"capital_district_cls",
"war_league",
"chat_language",

"_cs_labels",
"_cs_members",
"_cs_members_dict",
"_cs_capital_districts",
"_iter_labels",
"_iter_members",
"_iter_capital_districts"
)

def __init__(self, *, data, client, **_):
super().__init__(data=data, client=client)
self.label_cls = Label
self.member_cls = ClanMember

self._members = None # type: typing.Optional[typing.Dict[str, ClanMember]]
self.capital_district_cls = CapitalDistrict

self._from_data(data)

Expand Down Expand Up @@ -200,6 +207,13 @@ def _from_data(self, data: dict) -> None:
member_cls(data=mdata, client=self._client, clan=self) for mdata in data_get("memberList", [])
)

capital_district_cls = self.capital_district_cls
if data_get("clanCapital"):
self._iter_capital_districts = (capital_district_cls(data=cddata, client=self._client) for cddata in
data_get("clanCapital")["districts"])
else:
self._iter_capital_districts = ()

@cached_property("_cs_labels")
def labels(self) -> typing.List[Label]:
"""List[:class:`Label`]: A :class:`List` of :class:`Label` that the clan has."""
Expand All @@ -208,8 +222,18 @@ def labels(self) -> typing.List[Label]:
@cached_property("_cs_members")
def members(self) -> typing.List[ClanMember]:
"""List[:class:`ClanMember`]: A list of members that belong to the clan."""
dict_members = self._members = {m.tag: m for m in self._iter_members}
return list(dict_members.values())
return list(self.members_dict.values())

@cached_property("_cs_members_dict")
def members_dict(self) -> typing.Dict[str, ClanMember]:
"""Dict[str, :class:`ClanMember`]: A dict of members that belong to the clan."""
return {m.tag: m for m in self._iter_members}


@cached_property("_cs_capital_districts")
def capital_districts(self) -> typing.List[CapitalDistrict]:
"""List[:class:`CapitalDistrict`]: A :class:`List` of :class:`CapitalDistrict` that the clan has."""
return list(self._iter_capital_districts)

def get_member(self, tag: str) -> typing.Optional[ClanMember]:
"""Return a :class:`ClanMember` with the tag provided. Returns ``None`` if not found.
Expand All @@ -226,11 +250,11 @@ def get_member(self, tag: str) -> typing.Optional[ClanMember]:
The member who matches the tag provided: Optional[:class:`ClanMember`]
"""
tag = correct_tag(tag)
if not self._members:
if not self.members_dict:
_ = self.members

try:
return self._members[tag]
return self.members_dict[tag]
except KeyError:
return None

Expand Down
14 changes: 5 additions & 9 deletions coc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import asyncio
import logging

Expand Down Expand Up @@ -258,8 +257,8 @@ async def login(self, email: str, password: str) -> None:
self.http = http = self._create_client(email, password)
await http.create_session(self.connector, self.timeout)
await http.initialise_keys()
self._create_holders()

self._create_holders()
LOG.debug("HTTP connection created. Client is ready for use.")

def login_with_keys(self, *keys: str) -> None:
Expand All @@ -278,13 +277,10 @@ def login_with_keys(self, *keys: str) -> None:

LOG.debug("HTTP connection created. Client is ready for use.")

def close(self) -> None:
"""Closes the HTTP connection
"""
LOG.info("Clash of Clans client logging out...")
self.dispatch("on_client_close")
self.loop.run_until_complete(self.http.close())
self.loop.close()
async def close(self) -> None:
"""Closes the HTTP connection from within a loop function such as
async def main()"""
await self.http.close()

def dispatch(self, event_name: str, *args, **kwargs) -> None:
"""Dispatches an event listener matching the `event_name` parameter."""
Expand Down
4 changes: 3 additions & 1 deletion coc/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def __init__(self, response=None, data=None):
self.reason = None
self.message = response

fmt = "Unknown Error Occured: {0}"
fmt = "Error Occurred: {0}"
super().__init__(fmt.format(self.message))


Expand All @@ -102,6 +102,8 @@ class InvalidCredentials(HTTPException):
were passed. This is when your email/password pair is incorrect.
Subclass of :exc:`HTTPException`
"""
def __init__(self, response="Invalid Credentials"):
super().__init__(response=response)


class Forbidden(HTTPException):
Expand Down
12 changes: 4 additions & 8 deletions coc/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@


class Event:
"""Object that is created for an event. This contains runner functions, tags and type."""
"""
Object that is created for an event. This contains runner functions,
tags and type.
"""

__slots__ = ("runner", "callback", "tags", "type")

Expand Down Expand Up @@ -719,13 +722,6 @@ def run_forever(self):
except KeyboardInterrupt:
self.close()

def close(self):
"""Closes the client and all running tasks."""
tasks = {t for t in asyncio.Task.all_tasks(loop=self.loop) if not t.done()}
for task in tasks:
task.cancel()
super().close()

def dispatch(self, event_name: str, *args, **kwargs):
# pylint: disable=broad-except
registered = self._listeners["client"].get(event_name)
Expand Down
1 change: 0 additions & 1 deletion coc/events.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,3 @@ class EventsClient(Client):
def add_events(self, *events: Callable) -> None: ...
def remove_events(self, *events: Callable) -> None: ...
def run_forever(self) -> None: ...
def close(self) -> None: ...
Loading

0 comments on commit f8f414b

Please sign in to comment.