Skip to content

Commit

Permalink
Update dependencies 2024 (#123)
Browse files Browse the repository at this point in the history
* update pre-commit and rerun linting.

* update dependencies and handle Pydantic deprecations.

* update copyright.

* fix mypy errors.

* move exclude to root 'tool.ruff' level.

* change to extend-exclude (h/t @bandophahita).

* fix typos and comments and make a cooler dependency condition (h/t @bandophahita).

* update lock file, woops.
  • Loading branch information
perrygoy authored Feb 2, 2024
1 parent f3dac2d commit 68c1535
Show file tree
Hide file tree
Showing 17 changed files with 573 additions and 459 deletions.
7 changes: 3 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ files: '(screenpy|tests)/.*'
fail_fast: false
repos:
- repo: https://github.com/psf/black
rev: 23.11.0
rev: 24.1.1
hooks:
- id: black
language_version: python3.12
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.13
rev: v0.2.0
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.0
rev: v1.8.0
hooks:
- id: mypy
language_version: python3.12
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019-2023 Perry Goy
Copyright (c) 2019-2024 Perry Goy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"sphinx.ext.autosectionlabel",
"sphinx.ext.intersphinx",
"sphinxcontrib.autodoc_pydantic",
"sphinx_rtd_theme",
"autodoc_skip_protocols",
]

Expand Down Expand Up @@ -86,7 +87,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "default"
html_theme = "sphinx_rtd_theme"

# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
Expand Down
5 changes: 3 additions & 2 deletions docs/rtd-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
screenpy
importlib_metadata
autodoc_pydantic
importlib_metadata
screenpy
sphinx_rtd_theme
708 changes: 407 additions & 301 deletions poetry.lock

Large diffs are not rendered by default.

28 changes: 15 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ classifiers = [

[tool.poetry.dependencies]
importlib_metadata = {version = "*", python = "3.8.*"}
pydantic = "^1.10.7"
pydantic = "*"
pydantic-settings = "*"
PyHamcrest = ">=2.0.0"
python = "^3.8"
tomli = "^2.0.1"
tomli = {version = ">=2.0.1", python = "<3.11"}
typing_extensions = ">=4.8.0"

# convenience packages for development of screenpy only
Expand Down Expand Up @@ -122,6 +123,13 @@ test = [
[tool.ruff]
target-version = "py38" # minimum supported version
line-length = 88 # same as Black.
extend-exclude = [
"screenpy/__init__.py",
"screenpy/__version__.py",
"docs",
]

[tool.ruff.lint]
select = [
"A", # flake8-builtins
"ANN", # flake8-annotations # coming back to this one later to compare against mypy
Expand Down Expand Up @@ -168,14 +176,8 @@ ignore = [
"ANN101", # missing self annotation, we only annotate self when we return it.
"ANN102", # missing cls annotation, we only annotate cls when we return it.
]
exclude = [
"screenpy/__init__.py",
"screenpy/__version__.py",
"docs",
]


[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"tests/**" = [
"D", # we don't need public-API-polished docstrings in tests.
"FBT", # using a boolean as a test object is useful!
Expand All @@ -186,21 +188,21 @@ exclude = [
]


[tool.ruff.isort]
[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-comma = true
known-first-party = ["screenpy", "tests"]


[tool.ruff.flake8-pytest-style]
[tool.ruff.lint.flake8-pytest-style]
mark-parentheses = false


[tool.ruff.pycodestyle]
[tool.ruff.lint.pycodestyle]
ignore-overlong-task-comments = true


[tool.ruff.pydocstyle]
[tool.ruff.lint.pydocstyle]
convention = "google"


Expand Down
2 changes: 1 addition & 1 deletion screenpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ScreenPy is a composition-based test framework. It is inspired by the
SerenityBDD library for Java.
:copyright: (c) 2019–2023 by Perry Goy.
:copyright: (c) 2019–2024 by Perry Goy.
:license: MIT, see LICENSE for more details.
"""

Expand Down
2 changes: 1 addition & 1 deletion screenpy/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
__author__ = metadata["Author"]
__author_email__ = metadata["Author-email"]
__license__ = metadata["License"]
__copyright__ = f"2019-2023 {__author__}"
__copyright__ = f"2019-2024 {__author__}"
165 changes: 101 additions & 64 deletions screenpy/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import BaseSettings
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
)

if TYPE_CHECKING:
from typing import Any

from pydantic.env_settings import SettingsSourceCallable
from pydantic.fields import FieldInfo

if sys.version_info >= (3, 11):
try:
Expand All @@ -24,58 +28,87 @@
import tomli as tomllib


# The following pyproject.toml function was taken and adapted from Black:
# The logic in PyprojectTomlConfig was taken and adapted from Black:
# https://github.com/psf/black/blob/main/src/black/files.py


def _parse_pyproject_toml(tool_path: str) -> dict[str, Any]:
"""Parse a pyproject toml file, pulling out relevant parts for ScreenPy.
If parsing fails, will raise a tomllib.TOMLDecodeError.
Args:
tool_path: dotted-path for the tool, like "screenpy.stdoutadapter"
Returns:
Dict[str, Any]: the pyproject.toml settings under the tool_path.
"""
pyproject_path = Path.cwd() / "pyproject.toml"
if not pyproject_path.is_file():
return {}

with pyproject_path.open("rb") as f:
pyproject_toml = tomllib.load(f)
toml_config: dict[str, Any] = pyproject_toml.get("tool", {})
tool_steps = tool_path.split(".")
for subtool in tool_steps:
toml_config = toml_config.get(subtool, {})

return toml_config


def pyproject_settings(settings_class: BaseSettings) -> dict[str, Any]:
"""Retrieve the ``pyproject.toml`` settings for a ScreenPy settings class.
For more information, see Pydantic's documentation:
https://docs.pydantic.dev/usage/settings/#adding-sources
Args:
settings_class: the ScreenPy settings class to populate. This class
should have a ``_tool_path`` set, which will inform this function
of where to find its settings in pyproject.toml.
Returns:
Dict[str, Any]: the pyproject.toml settings for the settings_class.
"""
tool_path = getattr(settings_class, "_tool_path", "")
toml_config = _parse_pyproject_toml(tool_path)

allowed_keys = settings_class.schema()["properties"]
return {
k.replace("--", "").replace("-", "_"): v
for k, v in toml_config.items()
if k in allowed_keys
}
class PyprojectTomlConfig(PydanticBaseSettingsSource):
"""Load setting configuration from pyproject.toml."""

toml_config: dict[str, Any] | None

def __init__(self, settings_cls: type[BaseSettings]) -> None:
super().__init__(settings_cls)
self.toml_config = None

def _parse_pyproject_toml(self) -> dict[str, Any]:
"""Parse the pyproject.toml file and extract the relevant information."""
pyproject_path = Path.cwd() / "pyproject.toml"
if not pyproject_path.is_file():
self.toml_config = {}
else:
with pyproject_path.open("rb") as f:
pyproject_toml = tomllib.load(f)
toml_config: dict[str, Any] = pyproject_toml.get("tool", {})
if hasattr(self.settings_cls, "_tool_path"):
tool_path = self.settings_cls._tool_path.get_default()
else:
tool_path = ""
tool_steps = tool_path.split(".")
for subtool in tool_steps:
toml_config = toml_config.get(subtool, {})

self.toml_config = toml_config

return self.toml_config

def get_field_value(self, _: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
"""Retrieve the field's value from the toml config.
This method overrides an abstract method in the abstract base class.
Args:
field: the field to look up.
field_name: the name of the field to look up.
Returns:
(The value, the field_name, whether the value is a JSON object)
"""
if self.toml_config is None:
toml_config = self._parse_pyproject_toml()
else:
toml_config = self.toml_config

return toml_config.get(field_name), field_name, False

def prepare_field_value(
self,
field_name: str, # noqa: ARG002
field: FieldInfo, # noqa: ARG002
value: Any, # noqa: ANN401
value_is_complex: bool, # noqa: ARG002, FBT001
) -> Any: # noqa: ANN401
"""Return the value as-is, we do not need to prepare it.
This method overrides an abstract method in the abstract base class.
"""
return value

def __call__(self) -> dict[str, Any]:
"""Generate the config information."""
toml_config: dict[str, Any] = {}

for field_name, field in self.settings_cls.model_fields.items():
field_value, field_key, value_is_complex = self.get_field_value(
field, field_name
)
field_value = self.prepare_field_value(
field_name, field, field_value, value_is_complex=value_is_complex
)
if field_value is not None:
toml_config[field_key] = field_value

return toml_config


class ScreenPySettings(BaseSettings):
Expand All @@ -88,6 +121,7 @@ class ScreenPySettings(BaseSettings):
"""

_tool_path = "screenpy"
model_config = SettingsConfigDict(env_prefix="SCREENPY_", frozen=False)

TIMEOUT: float = 20
"""
Expand All @@ -107,19 +141,22 @@ class ScreenPySettings(BaseSettings):
all Narration. False by default.
"""

class Config: # noqa: D106
env_prefix = "SCREENPY_"
allow_mutation = True

@classmethod
def customise_sources(
cls,
init_settings: SettingsSourceCallable,
env_settings: SettingsSourceCallable,
file_secret_settings: SettingsSourceCallable,
) -> tuple[SettingsSourceCallable, ...]:
"""Set the order of preference of settings sources."""
return init_settings, env_settings, pyproject_settings, file_secret_settings
@classmethod
def settings_customise_sources( # noqa: PLR0913
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource, # noqa: ARG003
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Set the order of preference of settings sources."""
return (
init_settings,
env_settings,
PyprojectTomlConfig(settings_cls),
file_secret_settings,
)


# initialized instance
Expand Down
1 change: 0 additions & 1 deletion screenpy/given_when_then.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
explain more.
"""


from .actor import Actor


Expand Down
1 change: 0 additions & 1 deletion screenpy/narration/stdout_adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Default Adapters for the Narrator's microphone."""


from .configuration import settings
from .stdout_adapter import StdOutAdapter, StdOutManager

Expand Down
10 changes: 3 additions & 7 deletions screenpy/narration/stdout_adapter/configuration.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Define settings for the StdOutAdapter."""

from pydantic import BaseSettings
from pydantic_settings import SettingsConfigDict

from screenpy.configuration import ScreenPySettings


class StdOutAdapterSettings(BaseSettings):
class StdOutAdapterSettings(ScreenPySettings):
"""Settings for the StdOutAdapter.
To change these settings using environment variables, use the prefix
Expand All @@ -15,6 +15,7 @@ class StdOutAdapterSettings(BaseSettings):
"""

_tool_path = "screenpy.stdoutadapter"
model_config = SettingsConfigDict(env_prefix="SCREENPY_STDOUTADAPTER_")

INDENT_LOGS: bool = True
"""Whether or not to use indentation in logging."""
Expand All @@ -25,11 +26,6 @@ class StdOutAdapterSettings(BaseSettings):
INDENT_SIZE: int = 4
"""How many indent_chars to use for each level of indentation."""

class Config(ScreenPySettings.Config):
"""Inherit from the base Config but change the env_prefix."""

env_prefix = "SCREENPY_STDOUTADAPTER_"


# initialized instance
settings = StdOutAdapterSettings()
Loading

0 comments on commit 68c1535

Please sign in to comment.