Skip to content

Commit

Permalink
adding python 3.12 support (#115)
Browse files Browse the repository at this point in the history
* adding 3.12 support #114
  • Loading branch information
bandophahita authored Oct 25, 2023
1 parent 1a41ddf commit 54494c3
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 203 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest]

steps:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/poetry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
max-parallel: 9
fail-fast: false
matrix:
python-version: ["3.11"]
python-version: ["3.12"]
os: [ubuntu-latest]
poetry-version: ["1.3.2"]
poetry-version: ["1.6.1"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
max-parallel: 9
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest]

steps:
Expand Down
1 change: 1 addition & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ multi_line_output = 3
include_trailing_comma = True
use_parentheses = True
src_paths = screenpy,tests
extend_skip = docs
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,34 @@ requirements:
poetry export --without-hashes --with dev -f requirements.txt > requirements.txt

.PHONY: sync update_lock_only update check requirements

black-check:
black --check .

black:
black .

isort-check:
isort . --check

isort:
isort .

ruff:
ruff check .

ruff-fix:
ruff check . --fix --show-fixes

mypy:
mypy .

lint: isort-check ruff mypy

.PHONY: black-check black isort-check isort ruff ruff-fix mypy lint

pre-check-in: black-check lint

pre-check-in-fix: black isort ruff-fix mypy

.PHONY: pre-check-in pre-check-in-fix
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.black]
target-version = ['py311']
target-version = ['py312']

# This pyproject.toml is setup so it can be used with or without poetry and also
# supports editable installs (PEP 660) without breaking IDE and linter inspection.
Expand All @@ -10,6 +10,20 @@ target-version = ['py311']
# PIP:
# pip install -e .[dev]

extend-exclude = '''
# A regex preceded with ^/ will apply only to files and directories
# in the root of the project.
(
\.idea
| \.git
| \.mypy_cache
| \.tox
| \/docs
| ^/setup.py
)
'''

[tool.poetry]
name = "screenpy"
version = "4.2.1"
Expand Down
159 changes: 42 additions & 117 deletions screenpy/actions/silently.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,129 +2,20 @@

from __future__ import annotations

from typing import Any, TypeVar, Union, overload
import types
from typing import Any, TypeVar

from hamcrest.core.base_matcher import Matcher

from screenpy.actor import Actor
from screenpy.configuration import settings
from screenpy.exceptions import NotAnswerable, NotPerformable, NotResolvable
from screenpy.pacing import the_narrator
from screenpy.protocols import Answerable, Performable, Resolvable

T = TypeVar("T")


class SilentlyMixin:
"""Passthrough to the duck which is being silenced.
Silently needs to mimic the ducks (i.e. objects) they are wrapping.
All of the attributes from the "duck" should be exposed by the Silently object.
"""

def __getattr__(self, key: Any) -> Any:
"""Passthrough to the silenced duck's getattr."""
try:
return getattr(self.duck, key)
except AttributeError as exc:
msg = (
f"{self.__class__.__name__}({self.duck.__class__.__name__}) "
f"has no attribute '{key}'"
)
raise AttributeError(msg) from exc


class SilentlyPerformable(Performable, SilentlyMixin):
"""Perform the Performable, but quietly."""

def perform_as(self, actor: Actor) -> None:
"""Direct the Actor to perform silently."""
with the_narrator.mic_cable_kinked():
self.duck.perform_as(actor)
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return

def __init__(self, duck: Performable):
if not isinstance(duck, Performable):
msg = (
"SilentlyPerformable only works with Performables."
" Use `Silently` instead."
)
raise NotPerformable(msg)
self.duck = duck


class SilentlyAnswerable(Answerable, SilentlyMixin):
"""Answer the Answerable, but quietly."""

def answered_by(self, actor: Actor) -> Any:
"""Direct the Actor to answer the question silently."""
with the_narrator.mic_cable_kinked():
thing = self.duck.answered_by(actor)
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return thing

def __init__(self, duck: Answerable):
if not isinstance(duck, Answerable):
msg = (
"SilentlyAnswerable only works with Answerables."
" Use `Silently` instead."
)
raise NotAnswerable(msg)
self.duck = duck


class SilentlyResolvable(Resolvable, SilentlyMixin):
"""Resolve the Resolvable, but quietly."""

def resolve(self) -> Matcher:
"""Produce the Matcher to make the assertion, silently."""
with the_narrator.mic_cable_kinked():
res = self.duck.resolve()
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return res

def __init__(self, duck: Resolvable):
if not isinstance(duck, Resolvable):
msg = (
"SilentlyResolvable only works with Resolvables."
" Use `Silently` instead."
)
raise NotResolvable(msg)
self.duck = duck


T_duck = Union[
Answerable,
Performable,
Resolvable,
]
T_silent_duck = Union[
SilentlyAnswerable,
SilentlyPerformable,
SilentlyResolvable,
]


@overload
def Silently(duck: Performable) -> Union[Performable, SilentlyPerformable]:
...


@overload
def Silently(duck: Answerable) -> Union[Answerable, SilentlyAnswerable]:
...


@overload
def Silently(duck: Resolvable) -> Union[Resolvable, SilentlyResolvable]:
...


def Silently(duck: T_duck) -> Union[T_duck, T_silent_duck]:
def Silently(duck: T) -> T:
"""Silence the duck.
Any Performable, Answerable, or Resolvable wrapped in Silently will not be
Expand All @@ -134,8 +25,7 @@ def Silently(duck: T_duck) -> Union[T_duck, T_silent_duck]:
duck: Performable, Answerable, or Resolvable
Returns:
SilentlyPerformable, SilentlyAnswerable, or SilentlyResolvable
unless settings.UNABRIDGED_NARRATION is enabled.
Performable, Answerable, or Resolvable
Examples::
Expand All @@ -157,11 +47,46 @@ def Silently(duck: T_duck) -> Union[T_duck, T_silent_duck]:
if settings.UNABRIDGED_NARRATION:
return duck

# mypy really doesn't like monkeypatching
# See https://github.com/python/mypy/issues/2427

if isinstance(duck, Performable):
return SilentlyPerformable(duck)
original_perform_as = duck.perform_as

def perform_as(self: Performable, actor: Actor) -> None: # noqa: ARG001
"""Direct the Actor to perform silently."""
with the_narrator.mic_cable_kinked():
original_perform_as(actor)
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return

duck.perform_as = types.MethodType(perform_as, duck) # type: ignore[method-assign]

if isinstance(duck, Answerable):
return SilentlyAnswerable(duck)
original_answered_by = duck.answered_by

def answered_by(self: Answerable, actor: Actor) -> Any: # noqa: ARG001
"""Direct the Actor to answer the question silently."""
with the_narrator.mic_cable_kinked():
thing = original_answered_by(actor)
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return thing

duck.answered_by = types.MethodType(answered_by, duck) # type: ignore[method-assign]

if isinstance(duck, Resolvable):
return SilentlyResolvable(duck)
original_resolve = duck.resolve

def resolve(self: Resolvable) -> Matcher: # noqa: ARG001
"""Produce the Matcher to make the assertion, silently."""
with the_narrator.mic_cable_kinked():
res = original_resolve()
if not settings.UNABRIDGED_NARRATION:
the_narrator.clear_backup()
return res

duck.resolve = types.MethodType(resolve, duck) # type: ignore[method-assign]

return duck
Loading

0 comments on commit 54494c3

Please sign in to comment.