From b9ea7ead0b6f8b8ea39b14e08300b6f5e36e6298 Mon Sep 17 00:00:00 2001 From: Andrew Armbruster Date: Sun, 13 Aug 2023 20:16:38 +0200 Subject: [PATCH 1/2] chore: add type annotations and type checking Add type hinting, and use mypy to perform type checking as part of the CI checks. --- .github/workflows/pr.yml | 3 +++ Pipfile | 1 + Pipfile.lock | 38 +++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 ++ rps/game.py | 9 ++++++--- rps/play.py | 9 +++++---- rps/player.py | 8 +++++--- rps/strategy.py | 4 ++-- 8 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1f14faa..4d3a579 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -27,6 +27,9 @@ jobs: - name: Check formatting run: pipenv run black --target-version=py310 . + - name: Check types + run: pipenv run mypy rps rps-sim.py + - name: Run tests run: | pipenv run pytest diff --git a/Pipfile b/Pipfile index 118ea5e..82d3192 100644 --- a/Pipfile +++ b/Pipfile @@ -19,6 +19,7 @@ black = "*" pytest = "*" pytest-mock = "*" mypy-extensions = "*" +mypy = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 987bd57..a1e9c47 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6b1367d5919f0d578a501fd1a53545155842a4145ab195240d90fced34562864" + "sha256": "1dd7436d3bb287d8b187a5c5c9765431941777c6ccc27ed25fc9e24f85acec92" }, "pipfile-spec": 6, "requires": { @@ -150,6 +150,34 @@ "index": "pypi", "version": "==2.0.0" }, + "mypy": { + "hashes": [ + "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf", + "sha256:2c9d570f53908cbea326ad8f96028a673b814d9dca7515bf71d95fa662c3eb6f", + "sha256:35b13335c6c46a386577a51f3d38b2b5d14aa619e9633bb756bd77205e4bd09f", + "sha256:372fd97293ed0076d52695849f59acbbb8461c4ab447858cdaeaf734a396d823", + "sha256:42170e68adb1603ccdc55a30068f72bcfcde2ce650188e4c1b2a93018b826735", + "sha256:69b32d0dedd211b80f1b7435644e1ef83033a2af2ac65adcdc87c38db68a86be", + "sha256:725b57a19b7408ef66a0fd9db59b5d3e528922250fb56e50bded27fea9ff28f0", + "sha256:769ddb6bfe55c2bd9c7d6d7020885a5ea14289619db7ee650e06b1ef0852c6f4", + "sha256:79c520aa24f21852206b5ff2cf746dc13020113aa73fa55af504635a96e62718", + "sha256:84cf9f7d8a8a22bb6a36444480f4cbf089c917a4179fbf7eea003ea931944a7f", + "sha256:9166186c498170e1ff478a7f540846b2169243feb95bc228d39a67a1a450cdc6", + "sha256:a2500ad063413bc873ae102cf655bf49889e0763b260a3a7cf544a0cbbf7e70a", + "sha256:a551ed0fc02455fe2c1fb0145160df8336b90ab80224739627b15ebe2b45e9dc", + "sha256:ad3109bec37cc33654de8db30fe8ff3a1bb57ea65144167d68185e6dced9868d", + "sha256:b4ea3a0241cb005b0ccdbd318fb99619b21ae51bcf1660b95fc22e0e7d3ba4a1", + "sha256:c36011320e452eb30bec38b9fd3ba20569dc9545d7d4540d967f3ea1fab9c374", + "sha256:c8a7444d6fcac7e2585b10abb91ad900a576da7af8f5cffffbff6065d9115813", + "sha256:cbf18f8db7e5f060d61c91e334d3b96d6bb624ddc9ee8a1cde407b737acbca2c", + "sha256:d145b81a8214687cfc1f85c03663a5bbe736777410e5580e54d526e7e904f564", + "sha256:eec5c927aa4b3e8b4781840f1550079969926d0a22ce38075f6cfcf4b13e3eb4", + "sha256:f3460f34b3839b9bc84ee3ed65076eb827cd99ed13ed08d723f9083cada4a212", + "sha256:f3940cf5845b2512b3ab95463198b0cdf87975dfd17fdcc6ce9709a9abe09e69" + ], + "index": "pypi", + "version": "==1.5.0" + }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", @@ -213,6 +241,14 @@ ], "index": "pypi", "version": "==2.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + ], + "index": "pypi", + "version": "==4.7.1" } } } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f8ab000 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.mypy] +strict = true \ No newline at end of file diff --git a/rps/game.py b/rps/game.py index 09145c5..7899698 100644 --- a/rps/game.py +++ b/rps/game.py @@ -1,11 +1,14 @@ -def play_game(firstPlayer, secondPlayer): +from .player import Player +from .play import PlayResult + +def play_game(firstPlayer: Player, secondPlayer: Player) -> PlayResult: firstPlay = firstPlayer.play() secondPlay = secondPlayer.play() return firstPlay.match(secondPlay) -def play_games(firstPlayer, secondPlayer, times): +def play_games(firstPlayer: Player, secondPlayer: Player, times: int) -> list[PlayResult]: results = [] - for game in range(times): + for _ in range(times): results.append(play_game(firstPlayer, secondPlayer)) return results diff --git a/rps/play.py b/rps/play.py index 9595062..47897c5 100644 --- a/rps/play.py +++ b/rps/play.py @@ -7,7 +7,7 @@ class PlayResult(IntEnum): WIN = 1 -def human_readable(result): +def human_readable(result: PlayResult) -> str: if result == PlayResult.WIN: return "wins" elif result == PlayResult.LOSS: @@ -21,7 +21,7 @@ class Play(IntEnum): PAPER = 2 SCISSORS = 3 - def match(self, other): + def match(self, other: Play) -> PlayResult: if self.value == other.value or type(other) is not Play: return PlayResult.DRAW if self.value == self.ROCK: @@ -30,11 +30,12 @@ def match(self, other): return PlayResult.WIN if other.value == self.PAPER else PlayResult.LOSS elif self.value == self.PAPER: return PlayResult.WIN if other.value == self.ROCK else PlayResult.LOSS + return PlayResult.DRAW @staticmethod - def min(): + def min() -> int: return min([Play.ROCK, Play.PAPER, Play.SCISSORS]) @staticmethod - def max(): + def max() -> int: return max([Play.ROCK, Play.PAPER, Play.SCISSORS]) diff --git a/rps/player.py b/rps/player.py index ee73149..990517a 100644 --- a/rps/player.py +++ b/rps/player.py @@ -1,16 +1,18 @@ import abc + +from .play import Play from .strategy import PlaySelectionStrategy class Player(object): @abc.abstractmethod - def play(self): + def play(self) -> Play: """Required method""" class BotPlayer(Player): - def __init__(self, play_strategy): + def __init__(self, play_strategy: PlaySelectionStrategy): self.__strategy = play_strategy - def play(self): + def play(self) -> Play: return self.__strategy.play() diff --git a/rps/strategy.py b/rps/strategy.py index 38dddf2..8e4996b 100644 --- a/rps/strategy.py +++ b/rps/strategy.py @@ -7,10 +7,10 @@ class PlaySelectionStrategy(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod - def play(self): + def play(self) -> Play: """Required method""" class RandomPlayStrategy(PlaySelectionStrategy): - def play(self): + def play(self) -> Play: return Play(randrange(Play.min(), Play.max() + 1)) From dec3c8a460c4f17a6ee902995725861b5214a9d6 Mon Sep 17 00:00:00 2001 From: Andrew Armbruster Date: Sun, 13 Aug 2023 20:26:58 +0200 Subject: [PATCH 2/2] fix: NameError for self reference in method Fix an issue where referencing the class as a type caused a NameError. We do this by allowing forward references, see here: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#forward-references --- rps/play.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rps/play.py b/rps/play.py index 47897c5..196beda 100644 --- a/rps/play.py +++ b/rps/play.py @@ -1,3 +1,4 @@ +from __future__ import annotations from enum import IntEnum