Skip to content

Commit

Permalink
Merge pull request #99 from ikalnytskyi/chore/ruff
Browse files Browse the repository at this point in the history
Embrace Ruff linter, use ALL rules
  • Loading branch information
ikalnytskyi committed Jun 28, 2024
2 parents 7f836ce + 4e93fb4 commit f516c63
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 106 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# -- Project settings
project = "Picobox"
author = "Ihor Kalnytskyi"
copyright = "2017, Ihor Kalnytskyi"
project_copyright = "2017, Ihor Kalnytskyi"
release = importlib.metadata.version("picobox")
version = ".".join(release.split(".")[:2])

Expand Down
2 changes: 1 addition & 1 deletion examples/flask/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
# to test the app since any attempt to override dependencies in tests
# will fail due to later attempt to push a new box by request hooks.
picobox.push(box)
from example import app # noqa
from example import app
36 changes: 7 additions & 29 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ scripts.run = "python -m pytest --strict-markers {args:-vv}"

[tool.hatch.envs.lint]
detached = true
dependencies = ["ruff == 0.4.*"]
dependencies = ["ruff == 0.5.*"]
scripts.run = ["ruff check {args:.}", "ruff format --check --diff {args:.}"]

[tool.hatch.envs.type]
Expand All @@ -55,39 +55,17 @@ scripts.run = "sphinx-build -W -b html docs docs/_build/"
line-length = 100

[tool.ruff.lint]
select = [
"F",
"E",
"W",
"I",
"D",
"UP",
"S",
"FBT",
"B",
"C4",
"DTZ",
"T10",
"ISC",
"PIE",
"T20",
"PYI",
"PT",
"RET",
"SLF",
"SIM",
"TCH",
"ERA",
"RUF",
]
ignore = ["D107", "D203", "D213", "D401", "S101", "B904", "ISC001", "PT011", "SIM117"]
select = ["ALL"]
ignore = ["ANN", "PLR", "D107", "D203", "D213", "D401", "SIM117", "N801", "PLW2901", "PERF203", "COM812", "ISC001"]

[tool.ruff.lint.isort]
known-first-party = ["picobox"]

[tool.ruff.lint.per-file-ignores]
"examples/*" = ["I", "D", "T20"]
"tests/*" = ["D"]
"docs/*" = ["INP001"]
"examples/*" = ["I", "D", "T20", "INP001"]
"examples/flask/wsgi.py" = ["F401", "E402"]
"tests/*" = ["D", "S101", "ARG001", "BLE001", "INP001"]

[tool.mypy]
python_version = "3.8"
Expand Down
33 changes: 19 additions & 14 deletions src/picobox/_box.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Box container."""

from __future__ import annotations

import functools
import inspect
import threading
Expand Down Expand Up @@ -48,17 +50,17 @@ def do(magic):
"""

def __init__(self) -> None:
self._store: t.Dict[t.Hashable, t.Tuple[_scopes.Scope, t.Callable[[], t.Any]]] = {}
self._scope_instances: t.Dict[t.Type[_scopes.Scope], _scopes.Scope] = {}
self._store: dict[t.Hashable, tuple[_scopes.Scope, t.Callable[[], t.Any]]] = {}
self._scope_instances: dict[type[_scopes.Scope], _scopes.Scope] = {}
self._lock = threading.RLock()

def put(
self,
key: t.Hashable,
value: t.Any = _unset,
*,
factory: t.Optional[t.Callable[[], t.Any]] = None,
scope: t.Optional[t.Type[_scopes.Scope]] = None,
factory: t.Callable[[], t.Any] | None = None,
scope: type[_scopes.Scope] | None = None,
) -> None:
"""Define a dependency (aka service) within the box instance.
Expand All @@ -80,13 +82,16 @@ def put(
:raises ValueError: If both `value` and `factory` are passed.
"""
if value is _unset and factory is None:
raise TypeError("Box.put() missing 1 required argument: either 'value' or 'factory'")
error_message = "Box.put() missing 1 required argument: either 'value' or 'factory'"
raise TypeError(error_message)

if value is not _unset and factory is not None:
raise TypeError("Box.put() takes either 'value' or 'factory', not both")
error_message = "Box.put() takes either 'value' or 'factory', not both"
raise TypeError(error_message)

if value is not _unset and scope is not None:
raise TypeError("Box.put() takes 'scope' when 'factory' provided")
error_message = "Box.put() takes 'scope' when 'factory' provided"
raise TypeError(error_message)

def _factory() -> t.Any:
return value
Expand Down Expand Up @@ -168,8 +173,8 @@ def pass_(
self,
key: t.Hashable,
*,
as_: t.Optional[str] = None,
) -> "t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]":
as_: str | None = None,
) -> t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]:
r"""Pass a dependency to a function if nothing explicitly passed.
The decorator implements late binding which means it does not require
Expand All @@ -185,15 +190,15 @@ def pass_(
:raises KeyError: If no dependencies saved under `key` in the box.
"""

def decorator(fn: "t.Callable[P, R[T]]") -> "t.Callable[P, R[T]]":
def decorator(fn: t.Callable[P, R[T]]) -> t.Callable[P, R[T]]:
# If pass_ decorator is called second time (or more), we can squash
# the calls into one and reduce runtime costs of injection.
if hasattr(fn, "__dependencies__"):
fn.__dependencies__.append((key, as_))
return fn

@functools.wraps(fn)
def fn_with_dependencies(*args: "P.args", **kwargs: "P.kwargs") -> "R[T]":
def fn_with_dependencies(*args: P.args, **kwargs: P.kwargs) -> R[T]:
signature = inspect.signature(fn)
arguments = signature.bind_partial(*args, **kwargs)

Expand All @@ -212,7 +217,7 @@ def fn_with_dependencies(*args: "P.args", **kwargs: "P.kwargs") -> "R[T]":
if inspect.iscoroutinefunction(fn):

@functools.wraps(fn)
async def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> "T":
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
return await t.cast(t.Awaitable["T"], fn_with_dependencies(*args, **kwargs))
else:
wrapper = fn_with_dependencies # type: ignore[assignment]
Expand Down Expand Up @@ -264,8 +269,8 @@ def put(
key: t.Hashable,
value: t.Any = _unset,
*,
factory: t.Optional[t.Callable[[], t.Any]] = None,
scope: t.Optional[t.Type[_scopes.Scope]] = None,
factory: t.Callable[[], t.Any] | None = None,
scope: type[_scopes.Scope] | None = None,
) -> None:
"""Same as :meth:`Box.put` but applies to first underlying box."""
return self._boxes[0].put(key, value, factory=factory, scope=scope)
Expand Down
14 changes: 7 additions & 7 deletions src/picobox/_scopes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Scope interface and builtin implementations."""

from __future__ import annotations

import abc
import contextvars as _contextvars
import threading
Expand Down Expand Up @@ -35,7 +37,7 @@ class singleton(Scope):
"""Share instances across application."""

def __init__(self) -> None:
self._store: t.Dict[t.Hashable, t.Any] = {}
self._store: dict[t.Hashable, t.Any] = {}

def set(self, key: t.Hashable, value: t.Any) -> None:
self._store[key] = value
Expand All @@ -61,7 +63,7 @@ def get(self, key: t.Hashable) -> t.Any:
try:
rv = self._local.store[key]
except AttributeError:
raise KeyError(key)
raise KeyError(key) from None
return rv


Expand All @@ -77,13 +79,11 @@ class contextvars(Scope):
.. versionadded:: 2.1
"""

_store_obj: (
"weakref.WeakKeyDictionary[Scope, t.Dict[t.Hashable, _contextvars.ContextVar[t.Any]]]"
)
_store_obj: weakref.WeakKeyDictionary[Scope, dict[t.Hashable, _contextvars.ContextVar[t.Any]]]
_store_obj = weakref.WeakKeyDictionary()

@property
def _store(self) -> t.Dict[t.Hashable, _contextvars.ContextVar[t.Any]]:
def _store(self) -> dict[t.Hashable, _contextvars.ContextVar[t.Any]]:
try:
scope_store = self._store_obj[self]
except KeyError:
Expand All @@ -98,7 +98,7 @@ def get(self, key: t.Hashable) -> t.Any:
try:
return self._store[key].get()
except LookupError:
raise KeyError(key)
raise KeyError(key) from None


class noscope(Scope):
Expand Down
46 changes: 25 additions & 21 deletions src/picobox/_stack.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""Picobox API to work with a box at the top of the stack."""

from __future__ import annotations

import contextlib
import threading
import typing as t

from . import _scopes
from ._box import Box, ChainBox, _unset

if t.TYPE_CHECKING:
import typing_extensions

from ._scopes import Scope

P = typing_extensions.ParamSpec("P")
T = typing_extensions.TypeVar("T")
R = t.Union[T, t.Awaitable[T]]
Expand All @@ -26,19 +29,20 @@ def _create_push_context_manager(
try:
yield box
finally:
popped_box = pop_callback()

# Ensure the poped box is the same that was submitted by this exact
# context manager. It may happen if someone messed up with order of
# push() and pop() calls. Normally, push() should be used as a context
# manager to avoid this issue.
assert popped_box is box
if pop_callback() is not box:
error_message = (
"The .push() context manager has popped the wrong Box instance, "
"meaning it did not pop the one that was pushed. This could "
"occur if the .push() context manager is manipulated manually "
"instead of using the 'with' statement."
)
raise RuntimeError(error_message) from None


class _CurrentBoxProxy(Box):
"""Delegates operations to the Box instance at the top of the stack."""

def __init__(self, stack: t.List[Box]) -> None:
def __init__(self, stack: list[Box]) -> None:
self._stack = stack

def __getattribute__(self, name: str) -> t.Any:
Expand All @@ -48,7 +52,7 @@ def __getattribute__(self, name: str) -> t.Any:
try:
return getattr(self._stack[-1], name)
except IndexError:
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK)
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK) from None


class Stack:
Expand Down Expand Up @@ -95,9 +99,9 @@ def do(magic):
.. versionadded:: 2.2
"""

def __init__(self, name: t.Optional[str] = None) -> None:
def __init__(self, name: str | None = None) -> None:
self._name = name or f"0x{id(t):x}"
self._stack: t.List[Box] = []
self._stack: list[Box] = []
self._lock = threading.Lock()

# A proxy object that proxies all calls to a box instance on the top
Expand Down Expand Up @@ -157,15 +161,15 @@ def pop(self) -> Box:
try:
return self._stack.pop()
except IndexError:
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK)
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK) from None

def put(
self,
key: t.Hashable,
value: t.Any = _unset,
*,
factory: t.Optional[t.Callable[[], t.Any]] = None,
scope: t.Optional[t.Type[_scopes.Scope]] = None,
factory: t.Callable[[], t.Any] | None = None,
scope: type[Scope] | None = None,
) -> None:
"""The same as :meth:`Box.put` but for a box at the top of the stack."""
return self._current_box.put(key, value, factory=factory, scope=scope)
Expand All @@ -178,8 +182,8 @@ def pass_(
self,
key: t.Hashable,
*,
as_: t.Optional[str] = None,
) -> "t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]":
as_: str | None = None,
) -> t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]:
"""The same as :meth:`Box.pass_` but for a box at the top."""
return Box.pass_(self._current_box, key, as_=as_)

Expand Down Expand Up @@ -207,8 +211,8 @@ def put(
key: t.Hashable,
value: t.Any = _unset,
*,
factory: t.Optional[t.Callable[[], t.Any]] = None,
scope: t.Optional[t.Type[_scopes.Scope]] = None,
factory: t.Callable[[], t.Any] | None = None,
scope: type[Scope] | None = None,
) -> None:
"""The same as :meth:`Stack.put` but for a shared stack instance."""
return _instance.put(key, value, factory=factory, scope=scope)
Expand All @@ -222,7 +226,7 @@ def get(key: t.Hashable, default: t.Any = _unset) -> t.Any:
def pass_(
key: t.Hashable,
*,
as_: t.Optional[str] = None,
) -> "t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]":
as_: str | None = None,
) -> t.Callable[[t.Callable[P, R[T]]], t.Callable[P, R[T]]]:
"""The same as :meth:`Stack.pass_` but for a shared stack instance."""
return _instance.pass_(key, as_=as_)
Loading

0 comments on commit f516c63

Please sign in to comment.