Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented the Bender team classes #3

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
17 changes: 12 additions & 5 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,31 @@ def test(
) -> None:
session.debug("Running The tests.")
junit_file: Path = _PROJECT_ROOT / "junit.xml"
session.run(
# Added python -m as it did problems on windows.

# Prepare the command to run pytest
command = [
*get_python_prefix(),
"coverage",
"run",
f"--data-file={_COVERAGE_FILE!s}",
f"--source={_CODE_FILES!s}",
"-m",
"pytest",
f"{_PROJECT_ROOT!s}",
"--full-trace",
"--showlocals",
"--show-capture=all",
"-n",
"auto",
f"--junit-xml={junit_file!s}",
)
session.debug("Ran The tests.")
]

# Check for additional arguments (test files)
if session.posargs:
command.extend(session.posargs) # Add specific test files if provided

# Run the command
session.run(*command)



@nox.session(tags=["test", "ci"])
Expand Down
45 changes: 10 additions & 35 deletions tlab/air_bender.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,23 @@
import logging
from typing import Self

from .bender import Bender

class AirBender:
# Return type is None due to: https://peps.python.org/pep-0484/#the-meaning-of-annotations
def __init__(
self: Self,
name: str,
power: int,
) -> None:
raise NotImplementedError("You Should Implement this method")

@property
def name(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")
class AirBender(Bender):
base_skill = "Airbending"

@name.setter
def name(
# Return type is None due to: https://peps.python.org/pep-0484/#the-meaning-of-annotations
def __init__(
self: Self,
name: str,
) -> None:
raise NotImplementedError("You Should Implement this method")

@property
def power(
self: Self,
) -> int:
raise NotImplementedError("You Should Implement this method")

@power.setter
def power(
self: Self,
power: int,
*,
skill: str = base_skill,
) -> None:
raise NotImplementedError("You Should Implement this method")

@property
def skill(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")
super().__init__(name, power, skill=skill)

def bend(
self: Self,
) -> None:
raise NotImplementedError("You Should Implement this method")
logging.info(f"{self.name} is using his {self.skill.lower()} skill!")
66 changes: 66 additions & 0 deletions tlab/bender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from abc import ABC, abstractmethod
from typing import Self

from tlab.exceptions import InvalidPowerTypeError, InvalidPowerValueError


class Bender(ABC):
def __init__(
self: Self,
name: str,
power: int,
*,
skill: str,
) -> None:
self._validate_power(power)

self._name = name
self._power = power
self._skill = skill

@property
def name(
self: Self,
) -> str:
return self._name

@name.setter
def name(
self: Self,
name: str,
) -> None:
self._name = name

@property
def power(
self: Self,
) -> int:
return self._power

@power.setter
def power(
self: Self,
power: int,
) -> None:
self._validate_power(power)
self._power = power

@property
def skill(
self: Self,
) -> str:
return self._skill

@classmethod
def _validate_power(cls, power: int) -> None:
if type(power) is not int:
raise InvalidPowerTypeError("Power level must be a positive integer")

if power < 0:
raise InvalidPowerValueError("Power level must be a positive integer")

@abstractmethod
def bend(
self: Self,
) -> None:
pass
68 changes: 35 additions & 33 deletions tlab/earth_bender.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
import os
import re
import sys
from typing import Self
class EarthBender:

from .bender import Bender


class EarthBender(Bender):
base_skill = "Earthbending"
attack_var_name = "EARTH_ATTACK"
expected_attack = r"rock ball"
expected_attack_regex = re.compile(
expected_attack,
re.IGNORECASE,
)
unexpected_attack_var_value = "No Rock Ball :("

# Return type is None due to: https://peps.python.org/pep-0484/#the-meaning-of-annotations
def __init__(
self: Self,
name: str,
power: int,
*,
skill: str = base_skill,
) -> None:
raise NotImplementedError("You Should Implement this method")
super().__init__(name, power, skill=skill)

@property
def name(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")

@name.setter
def name(
def bend(
self: Self,
name: str,
) -> None:
raise NotImplementedError("You Should Implement this method")
attack = os.environ[EarthBender.attack_var_name]

@property
def power(
self: Self,
) -> int:
raise NotImplementedError("You Should Implement this method")
if re.fullmatch(self.expected_attack_regex, attack):
self.emit_expected_attack()
return

@power.setter
def power(
self: Self,
power: int,
) -> None:
raise NotImplementedError("You Should Implement this method")

@property
def skill(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")
os.environ[EarthBender.attack_var_name] = (
EarthBender.unexpected_attack_var_value
)

def bend(
self: Self,
) -> None:
raise NotImplementedError("You Should Implement this method")
def emit_expected_attack(self) -> None:
print(
f"{self.expected_attack} with power: {self.power:^2}.",
end="",
file=sys.stdout,
flush=True,
)
63 changes: 28 additions & 35 deletions tlab/fire_bender.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,43 @@
import random
from typing import Self
from typing import Never, Self

from .bender import Bender
from .handler_manager import HandlerManager


class FireBender(Bender, HandlerManager):
base_skill = "Firebending"
attacked_name = "dead"

# special days
eclipse_day = 0
sozins_comet_day = 6

class FireBender:
# Return type is None due to: https://peps.python.org/pep-0484/#the-meaning-of-annotations
def __init__(
self: Self,
name: str,
power: int,
random_generator: random.Random = random,
) -> None:
raise NotImplementedError("You Should Implement this method")

@property
def name(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")

@name.setter
def name(
self: Self,
name: str,
generator: random.Random | None = None,
*,
skill: str = base_skill,
) -> None:
raise NotImplementedError("You Should Implement this method")
super().__init__(name, power, skill=skill)
if generator is None:
generator = random.Random()

@property
def power(
self: Self,
) -> int:
raise NotImplementedError("You Should Implement this method")
self.generator = generator

@power.setter
def power(
def bend(
self: Self,
power: int,
) -> None:
raise NotImplementedError("You Should Implement this method")
value = self.generator.randint(0, 6)
self.handle(key=value)

@property
def skill(
self: Self,
) -> str:
raise NotImplementedError("You Should Implement this method")
@HandlerManager.register_handler(key=eclipse_day)
def _eclipse_attack(self) -> None:
self.name = self.attacked_name

def bend(
self: Self,
) -> None:
raise NotImplementedError("You Should Implement this method")
@HandlerManager.register_handler(key=sozins_comet_day)
def _sozins_comet_attack(self) -> Never:
raise SystemExit(self.sozins_comet_day)
26 changes: 26 additions & 0 deletions tlab/handler_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from collections.abc import Callable
from typing import Any, ClassVar


class HandlerManager[K, V]:
"""
Generic mixin class that allows registering methods as handlers referenced by a key
"""

handlers: ClassVar[dict[Any, Callable]] = {} # dictionary of handlers

@classmethod
def register_handler(cls, key: K) -> Callable:
def decorator(func: Callable) -> Callable:
cls.handlers[key] = func
return func

return decorator

def handle(self, key: K, *args: list) -> V:
handler = self.handlers.get(key)

if handler is not None:
return self.handlers[key](self, *args)

return None
45 changes: 45 additions & 0 deletions tlab/link_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from abc import ABC, abstractmethod
from collections.abc import Iterable
from typing import Any, ClassVar


class LinkManager(ABC):
@abstractmethod
def link(self, link: str) -> str:
pass

@abstractmethod
def fallover_links(self) -> Iterable[str]:
pass


class DefaultLinkManager(LinkManager):
# links to open on matching patterns
default_links: ClassVar[dict[Any, str]] = {
"FULL": "https://www.wikiwand.com/en/6",
None: "https://youtu.be/gk-aCL6eyGc?si=XX45XZzc3a8uCN0o&t={}",
}

# links to open when on no matching patterns
default_failover_links: ClassVar[set[str]] = {
"https://youtu.be/weZKm1kTrpc?si=_Unblsn5tPvzwfs7",
}

def __init__(
self,
links: dict[Any, str] | None = None,
fallover_links: set[str] | None = None,
) -> None:
if links is None:
links = self.default_links
if fallover_links is None:
fallover_links = self.default_failover_links

self._links = links
self._fallover_links = fallover_links

def link(self, link: str) -> str:
return self._links.get(link)

def fallover_links(self) -> Iterable[str]:
return self._fallover_links
Loading