Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
BUILDDIR = _build

# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
Expand Down
64 changes: 33 additions & 31 deletions doc/developing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,19 @@ install the `pre-commit hook`_ for Black integration before committing::
Running the tests
-----------------

You can run the tests using `py.test`_::
You can run the tests using `pytest`_::

$ py.test
$ pytest

To generate test coverage information as HTML do::

$ py.test --cov --cov-report html
$ pytest --cov --cov-report html

You can then point your web browser to the ``htmlcov/index.html`` file
in the project directory and click on modules to see detailed coverage
information.

.. _`py.test`: https://pytest.org/latest/
.. _`pytest`: https://pytest.org

Black
-----
Expand All @@ -80,6 +80,30 @@ Black has also integration_ for the most popular editors.
.. _`Black Code Formatter`: https://black.readthedocs.io
.. _integration: https://black.readthedocs.io/en/stable/editor_integration.html

flake8
------

flake8_ is a tool that can do various checks for common Python
mistakes using pyflakes_, checks for PEP8_ style compliance and
can do `cyclomatic complexity`_ checking. To do pyflakes and pep8
checking do::

$ flake8 reg

To also show cyclomatic complexity, use this command::

$ flake8 --max-complexity=10 reg


.. _flake8: https://pypi.org/project/flake8

.. _pyflakes: https://pypi.org/project/pyflakes

.. _pep8: https://peps.python.org/pep-0008

.. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity


Running the documentation tests
-------------------------------

Expand Down Expand Up @@ -113,44 +137,22 @@ Or from the Reg project directory::

$ make -C doc html

Various checking tools
----------------------

flake8_ is a tool that can do various checks for common Python
mistakes using pyflakes_, check for PEP8_ style compliance and
can do `cyclomatic complexity`_ checking. To do pyflakes and pep8
checking do::

$ flake8 reg

To also show cyclomatic complexity, use this command::

$ flake8 --max-complexity=10 reg

.. _flake8: https://pypi.org/project/flake8

.. _pyflakes: https://pypi.org/project/pyflakes

.. _pep8: https://peps.python.org/pep-0008

.. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity

Tox
---

With tox you can test Morepath under different Python environments.
With tox you can test Reg under different Python environments.

We have Travis continuous integration installed on Morepath's github
We have gh-actions continuous integration installed on Reg's github
repository and it runs the same tox tests after each checkin.

First you should install all Python versions which you want to
test. The versions which are not installed will be skipped. You should
at least install Python 3.7 which is required by flake8, coverage and
doctests.
at least install Python 3.14 which is required by flake8, coverage,
doctests, mypy and pyright.

One tool you can use to install multiple versions of Python is pyenv_.

To find out which test environments are defined for Morepath in tox.ini run::
To find out which test environments are defined for Reg in tox.ini run::

$ tox -l

Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,23 @@ show_missing = true

[tool.flake8]
show-source = true
ignore = ["E203", "E301", "E501", "E704", "E731", "W503"]
ignore = ["E203", "E301", "E302", "E501", "E704", "E731", "W503"]
extend-select = ["TC", "TC1"]
max-line-length = 88

[tool.mypy]
python_version = "3.10"
strict = true
files = ["."]
mypy_path = "stubs"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mypy_path = "stubs"
mypy_path = "$MYPY_CONFIG_FILE_DIR/stubs"

This is more reliable, otherwise mypy will only work correctly when called from the repository's root directory. But as pointed out above we're probably better off not having any third party stubs in a package with downstream dependencies and even better off, if we get rid of the repoze.lru dependency entirely.

explicit_package_bases = true
warn_unreachable = true
warn_unused_ignores = true
warn_unused_configs = true

[tool.pyright]
typeCheckingMode = "strict"
stubPath = "stubs"

[tool.tox]
requires = ["tox>=4"]
Expand Down
46 changes: 11 additions & 35 deletions reg/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, Generic

from repoze.lru import lru_cache # type: ignore[import-untyped]
from repoze.lru import lru_cache

if TYPE_CHECKING:
from collections.abc import Callable, Sequence
Expand Down Expand Up @@ -49,27 +49,13 @@ class DictCachingKeyLookup(Generic[_ValueT]):

def __init__(self, key_lookup: KeyLookup[_ValueT]) -> None:
self.key_lookup = key_lookup
self.component = Cache(key_lookup.component).__getitem__ # type: ignore
self.fallback = Cache(key_lookup.fallback).__getitem__ # type: ignore
self.component = Cache(key_lookup.component).__getitem__
self.fallback = Cache(key_lookup.fallback).__getitem__

def _all(key: Sequence[Any]) -> list[_ValueT]:
return list(key_lookup.all(key))

self.all = Cache(_all).__getitem__ # type: ignore

if TYPE_CHECKING:
# NOTE: For pyright's sake we declare these callable instance attributes
# as methods, even though they're not, since pyright does not seem
# to be able to match protocols against them. mypy can deal with
# it just fine
def component(self, key: Sequence[Any], /) -> _ValueT | None:
raise NotImplementedError

def fallback(self, key: Sequence[Any], /) -> _ValueT | None:
raise NotImplementedError

def all(self, key: Sequence[Any], /) -> list[_ValueT]:
raise NotImplementedError
self.all = Cache(_all).__getitem__


class LruCachingKeyLookup(Generic[_ValueT]):
Expand Down Expand Up @@ -100,20 +86,10 @@ def __init__(
fallback_cache_size: int,
) -> None:
self.key_lookup = key_lookup
self.component = lru_cache(component_cache_size)(key_lookup.component) # type: ignore
self.fallback = lru_cache(fallback_cache_size)(key_lookup.fallback) # type: ignore
self.all = lru_cache(all_cache_size)(lambda key: list(key_lookup.all(key))) # type: ignore

if TYPE_CHECKING:
# NOTE: For pyright's sake we declare these callable instance attributes
# as methods, even though they're not, since pyright does not seem
# to be able to match protocols against them. mypy can deal with
# it just fine
def component(self, key: Sequence[Any], /) -> _ValueT | None:
raise NotImplementedError

def fallback(self, key: Sequence[Any], /) -> _ValueT | None:
raise NotImplementedError

def all(self, key: Sequence[Any], /) -> list[_ValueT]:
raise NotImplementedError
self.component = lru_cache(component_cache_size)(key_lookup.component)
self.fallback = lru_cache(fallback_cache_size)(key_lookup.fallback)

def _all(key: Sequence[Any]) -> list[_ValueT]:
return list(key_lookup.all(key))

self.all = lru_cache(all_cache_size)(_all)
69 changes: 69 additions & 0 deletions stubs/repoze/lru/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from collections.abc import Callable
from typing import Any, TypeVar, overload

_F = TypeVar("_F", bound=Callable[..., Any])
_T = TypeVar("_T")

class CacheSizeMustBeGreaterThanZero(ValueError):
def __init__(self, size: int) -> None: ...

class CacheMaxsizeRequired(ValueError):
def __init__(self) -> None: ...

class CacheAlreadyInUse(KeyError):
def __init__(self, name: str) -> None: ...

class Cache:
def clear(self) -> None: ...
@overload
def get(self, key: Any) -> Any | None: ...
@overload
def get(self, key: Any, default: _T) -> Any | _T: ...
def put(self, key: Any, val: Any) -> None: ...
def invalidate(self, key: Any) -> None: ...

class UnboundedCache(Cache):
def __init__(self) -> None: ...

class LRUCache(Cache):
size: int
evictions: int
hits: int
misses: int
lookups: int
def __init__(self, size: int) -> None: ...

class ExpiringLRUCache(Cache):
size: int
default_timeout: float
evictions: int
hits: int
misses: int
lookups: int
def __init__(self, size: int, default_timeout: float = ...) -> None: ...
def put(self, key: Any, val: Any, timeout: float | None = None) -> None: ...

class lru_cache:
cache: Cache
def __init__(
self,
maxsize: int | None,
cache: Cache | None = None,
timeout: float | None = None,
ignore_unhashable_args: bool = False,
) -> None: ...
def __call__(self, func: _F) -> _F: ...

class CacheMaker:
def __init__(self, maxsize: int | None = None, timeout: float = ...) -> None: ...
def memoized(self, name: str | None = None) -> lru_cache: ...
def lrucache(
self, name: str | None = None, maxsize: int | None = None
) -> lru_cache: ...
def expiring_lrucache(
self,
name: str | None = None,
maxsize: int | None = None,
timeout: float | None = None,
) -> lru_cache: ...
def clear(self, *names: str) -> None: ...
Loading