From a9c8111d24ba505f6eeae70d768f273c17af6f87 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 19 Jan 2023 17:27:01 -0800 Subject: switch to pyproject.toml --- .flake8 | 25 ++++++++++ .github/workflows/tests.yaml | 2 +- CHANGES.rst | 3 ++ pyproject.toml | 68 ++++++++++++++++++++++++++ setup.cfg | 111 ------------------------------------------- setup.py | 8 ---- 6 files changed, 97 insertions(+), 120 deletions(-) create mode 100644 .flake8 create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..db529bd --- /dev/null +++ b/.flake8 @@ -0,0 +1,25 @@ +[flake8] +extend-select = + # bugbear + B + # bugbear opinions + B9 + # implicit str concat + ISC +extend-ignore = + # slice notation whitespace, invalid + E203 + # line length, handled by bugbear B950 + E501 + # bare except, handled by bugbear B001 + E722 + # zip with strict=, requires python >= 3.10 + B905 + # string formatting opinion, B028 renamed to B907 + B028 + B907 +# up to 88 allowed by bugbear B950 +max-line-length = 80 +per-file-ignores = + # __init__ exports names + src/jinja2/__init__.py: F401 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b757fa0..85e748b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -50,7 +50,7 @@ jobs: uses: actions/cache@v3 with: path: ./.mypy_cache - key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }} + key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }} if: matrix.tox == 'typing' - run: pip install tox - run: tox run -e ${{ matrix.tox }} diff --git a/CHANGES.rst b/CHANGES.rst index 7ee75a6..d539a73 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Version 3.2.0 Unreleased +- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. + :pr:`1793` + Version 3.1.2 ------------- diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f90576d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,68 @@ +[project] +name = "Jinja2" +description = "A very fast and expressive template engine." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +authors = [{name = "Armin Ronacher", email = "armin.ronacher@active-4.com"}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Text Processing :: Markup :: HTML", +] +requires-python = ">=3.7" +dependencies = ["MarkupSafe>=2.0"] +dynamic = ["version"] + +[project.urls] +Donate = "https://palletsprojects.com/donate" +Documentation = "https://jinja.palletsprojects.com/" +Changes = "https://jinja.palletsprojects.com/changes/" +"Source Code" = "https://github.com/pallets/jinja/" +"Issue Tracker" = "https://github.com/pallets/jinja/issues/" +Twitter = "https://twitter.com/PalletsTeam" +Chat = "https://discord.gg/pallets" + +[project.optional-dependencies] +i18n = ["Babel>=2.7"] + +[project.entry-points."babel.extractors"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +version = {attr = "jinja2.__version__"} + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["jinja2", "tests"] + +[tool.coverage.paths] +source = ["src", "*/site-packages"] + +[tool.mypy] +python_version = "3.7" +files = ["src/jinja2"] +show_error_codes = true +pretty = true +strict = true +local_partial_types = true +warn_unreachable = true + +[[tool.mypy.overrides]] +module = [ + "jinja2.defaults", + "markupsafe", +] +no_implicit_reexport = false diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index edfa309..0000000 --- a/setup.cfg +++ /dev/null @@ -1,111 +0,0 @@ -[metadata] -name = Jinja2 -version = attr: jinja2.__version__ -url = https://palletsprojects.com/p/jinja/ -project_urls = - Donate = https://palletsprojects.com/donate - Documentation = https://jinja.palletsprojects.com/ - Changes = https://jinja.palletsprojects.com/changes/ - Source Code = https://github.com/pallets/jinja/ - Issue Tracker = https://github.com/pallets/jinja/issues/ - Twitter = https://twitter.com/PalletsTeam - Chat = https://discord.gg/pallets -license = BSD-3-Clause -license_files = LICENSE.rst -author = Armin Ronacher -author_email = armin.ronacher@active-4.com -maintainer = Pallets -maintainer_email = contact@palletsprojects.com -description = A very fast and expressive template engine. -long_description = file: README.rst -long_description_content_type = text/x-rst -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Text Processing :: Markup :: HTML - -[options] -packages = find: -package_dir = = src -include_package_data = True -python_requires = >= 3.7 -# Dependencies are in setup.py for GitHub's dependency graph. - -[options.packages.find] -where = src - -[options.entry_points] -babel.extractors = - jinja2 = jinja2.ext:babel_extract[i18n] - -[tool:pytest] -testpaths = tests -filterwarnings = - error - # Python 3.9 raises a deprecation from internal asyncio code. - ignore:The loop argument:DeprecationWarning:asyncio[.]base_events:542 - -[coverage:run] -branch = True -source = - jinja2 - tests - -[coverage:paths] -source = - src - */site-packages - -[flake8] -# B = bugbear -# E = pycodestyle errors -# F = flake8 pyflakes -# W = pycodestyle warnings -# B9 = bugbear opinions -# ISC = implicit str concat -select = B, E, F, W, B9, ISC -ignore = - # slice notation whitespace, invalid - E203 - # line length, handled by bugbear B950 - E501 - # bare except, handled by bugbear B001 - E722 - # bin op line break, invalid - W503 - # zip with strict=, requires python >= 3.10 - B905 -# up to 88 allowed by bugbear B950 -max-line-length = 80 -per-file-ignores = - # __init__ exports names - src/jinja2/__init__.py: F401 - -[mypy] -files = src/jinja2 -python_version = 3.7 -show_error_codes = True -disallow_subclassing_any = True -disallow_untyped_calls = True -disallow_untyped_defs = True -disallow_incomplete_defs = True -no_implicit_optional = True -local_partial_types = True -no_implicit_reexport = True -strict_equality = True -warn_redundant_casts = True -warn_unused_configs = True -warn_unused_ignores = True -warn_return_any = True -warn_unreachable = True - -[mypy-jinja2.defaults] -no_implicit_reexport = False - -[mypy-markupsafe] -no_implicit_reexport = False diff --git a/setup.py b/setup.py deleted file mode 100644 index 79d0708..0000000 --- a/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -from setuptools import setup - -# Metadata goes in setup.cfg. These are here for GitHub's dependency graph. -setup( - name="Jinja2", - install_requires=["MarkupSafe>=2.0"], - extras_require={"i18n": ["Babel>=2.7"]}, -) -- cgit v1.2.1 From 614b045fab971de8b96853dbdd9b9c6c234de32f Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 19 Jan 2023 17:27:15 -0800 Subject: ignore bugbear opinion --- .flake8 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.flake8 b/.flake8 index db529bd..705245b 100644 --- a/.flake8 +++ b/.flake8 @@ -23,3 +23,6 @@ max-line-length = 80 per-file-ignores = # __init__ exports names src/jinja2/__init__.py: F401 + # not relevant to jinja's compiler + src/jinja2/compiler.py: B906 + src/jinja2/idtracking.py: B906 -- cgit v1.2.1 From 8ed8e1d0eda956aa0cdc6e216270be3ebcfd4b4a Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 19 Jan 2023 17:49:35 -0800 Subject: fix mypy strict findings --- src/jinja2/async_utils.py | 2 +- src/jinja2/environment.py | 12 +++++++----- src/jinja2/filters.py | 42 +++++++++++++++++++++++------------------- src/jinja2/lexer.py | 2 +- src/jinja2/loaders.py | 9 +++++++-- src/jinja2/nodes.py | 2 +- src/jinja2/parser.py | 11 +++++++++-- src/jinja2/runtime.py | 5 ++++- src/jinja2/sandbox.py | 6 +++--- src/jinja2/tests.py | 2 +- src/jinja2/utils.py | 6 ++++-- 11 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/jinja2/async_utils.py b/src/jinja2/async_utils.py index 715d701..e65219e 100644 --- a/src/jinja2/async_utils.py +++ b/src/jinja2/async_utils.py @@ -47,7 +47,7 @@ def async_variant(normal_func): # type: ignore if need_eval_context: wrapper = pass_eval_context(wrapper) - wrapper.jinja_async_variant = True + wrapper.jinja_async_variant = True # type: ignore[attr-defined] return wrapper return decorator diff --git a/src/jinja2/environment.py b/src/jinja2/environment.py index 88b2666..29a1b4e 100644 --- a/src/jinja2/environment.py +++ b/src/jinja2/environment.py @@ -79,7 +79,7 @@ def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_b def create_cache( size: int, -) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[BaseLoader]", str], "Template"]]: """Return the cache class for the given size.""" if size == 0: return None @@ -91,8 +91,10 @@ def create_cache( def copy_cache( - cache: t.Optional[t.MutableMapping], -) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]: + cache: t.Optional[ + t.MutableMapping[t.Tuple["weakref.ref[BaseLoader]", str], "Template"] + ], +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[BaseLoader]", str], "Template"]]: """Create an empty copy of the given cache.""" if cache is None: return None @@ -814,7 +816,7 @@ class Environment: def compile_templates( self, - target: t.Union[str, os.PathLike], + target: t.Union[str, "os.PathLike[str]"], extensions: t.Optional[t.Collection[str]] = None, filter_func: t.Optional[t.Callable[[str], bool]] = None, zip: t.Optional[str] = "deflated", @@ -1588,7 +1590,7 @@ class TemplateStream: def dump( self, - fp: t.Union[str, t.IO], + fp: t.Union[str, t.IO[t.Any]], encoding: t.Optional[str] = None, errors: t.Optional[str] = "strict", ) -> None: diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py index ed07c4c..f4479ff 100644 --- a/src/jinja2/filters.py +++ b/src/jinja2/filters.py @@ -538,7 +538,7 @@ def do_default( @pass_eval_context def sync_do_join( eval_ctx: "EvalContext", - value: t.Iterable, + value: t.Iterable[t.Any], d: str = "", attribute: t.Optional[t.Union[str, int]] = None, ) -> str: @@ -596,7 +596,7 @@ def sync_do_join( @async_variant(sync_do_join) # type: ignore async def do_join( eval_ctx: "EvalContext", - value: t.Union[t.AsyncIterable, t.Iterable], + value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], d: str = "", attribute: t.Optional[t.Union[str, int]] = None, ) -> str: @@ -1146,7 +1146,7 @@ def do_round( class _GroupTuple(t.NamedTuple): grouper: t.Any - list: t.List + list: t.List[t.Any] # Use the regular tuple repr to hide this subclass if users print # out the value during debugging. @@ -1402,26 +1402,30 @@ def do_attr( @typing.overload def sync_do_map( - context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any -) -> t.Iterable: + context: "Context", + value: t.Iterable[t.Any], + name: str, + *args: t.Any, + **kwargs: t.Any, +) -> t.Iterable[t.Any]: ... @typing.overload def sync_do_map( context: "Context", - value: t.Iterable, + value: t.Iterable[t.Any], *, attribute: str = ..., default: t.Optional[t.Any] = None, -) -> t.Iterable: +) -> t.Iterable[t.Any]: ... @pass_context def sync_do_map( - context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any -) -> t.Iterable: + context: "Context", value: t.Iterable[t.Any], *args: t.Any, **kwargs: t.Any +) -> t.Iterable[t.Any]: """Applies a filter on a sequence of objects or looks up an attribute. This is useful when dealing with lists of objects but you are really only interested in a certain value of it. @@ -1471,32 +1475,32 @@ def sync_do_map( @typing.overload def do_map( context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], + value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], name: str, *args: t.Any, **kwargs: t.Any, -) -> t.Iterable: +) -> t.Iterable[t.Any]: ... @typing.overload def do_map( context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], + value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], *, attribute: str = ..., default: t.Optional[t.Any] = None, -) -> t.Iterable: +) -> t.Iterable[t.Any]: ... @async_variant(sync_do_map) # type: ignore async def do_map( context: "Context", - value: t.Union[t.AsyncIterable, t.Iterable], + value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], *args: t.Any, **kwargs: t.Any, -) -> t.AsyncIterable: +) -> t.AsyncIterable[t.Any]: if value: func = prepare_map(context, args, kwargs) @@ -1689,7 +1693,7 @@ def do_tojson( def prepare_map( - context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any] + context: "Context", args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any] ) -> t.Callable[[t.Any], t.Any]: if not args and "attribute" in kwargs: attribute = kwargs.pop("attribute") @@ -1718,7 +1722,7 @@ def prepare_map( def prepare_select_or_reject( context: "Context", - args: t.Tuple, + args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any], modfunc: t.Callable[[t.Any], t.Any], lookup_attr: bool, @@ -1753,7 +1757,7 @@ def prepare_select_or_reject( def select_or_reject( context: "Context", value: "t.Iterable[V]", - args: t.Tuple, + args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any], modfunc: t.Callable[[t.Any], t.Any], lookup_attr: bool, @@ -1769,7 +1773,7 @@ def select_or_reject( async def async_select_or_reject( context: "Context", value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", - args: t.Tuple, + args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any], modfunc: t.Callable[[t.Any], t.Any], lookup_attr: bool, diff --git a/src/jinja2/lexer.py b/src/jinja2/lexer.py index aff7e9f..16ca73e 100644 --- a/src/jinja2/lexer.py +++ b/src/jinja2/lexer.py @@ -447,7 +447,7 @@ def get_lexer(environment: "Environment") -> "Lexer": return lexer -class OptionalLStrip(tuple): +class OptionalLStrip(tuple): # type: ignore[type-arg] """A special tuple for marking a point in the state that can have lstrip applied. """ diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py index 57b5582..9b479be 100644 --- a/src/jinja2/loaders.py +++ b/src/jinja2/loaders.py @@ -177,7 +177,9 @@ class FileSystemLoader(BaseLoader): def __init__( self, - searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]], + searchpath: t.Union[ + str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] + ], encoding: str = "utf-8", followlinks: bool = False, ) -> None: @@ -598,7 +600,10 @@ class ModuleLoader(BaseLoader): has_source_access = False def __init__( - self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]] + self, + path: t.Union[ + str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] + ], ) -> None: package_name = f"_jinja2_module_templates_{id(self):x}" diff --git a/src/jinja2/nodes.py b/src/jinja2/nodes.py index b2f88d9..00365ed 100644 --- a/src/jinja2/nodes.py +++ b/src/jinja2/nodes.py @@ -56,7 +56,7 @@ class NodeType(type): def __new__(mcs, name, bases, d): # type: ignore for attr in "fields", "attributes": - storage = [] + storage: t.List[t.Any] = [] storage.extend(getattr(bases[0] if bases else object, attr, ())) storage.extend(d.get(attr, ())) assert len(bases) <= 1, "multiple inheritance not allowed" diff --git a/src/jinja2/parser.py b/src/jinja2/parser.py index cefce2d..fb4fd0d 100644 --- a/src/jinja2/parser.py +++ b/src/jinja2/parser.py @@ -859,7 +859,14 @@ class Parser: return nodes.Slice(lineno=lineno, *args) - def parse_call_args(self) -> t.Tuple: + def parse_call_args( + self, + ) -> t.Tuple[ + t.List[nodes.Expr], + t.List[nodes.Keyword], + t.Union[nodes.Expr, None], + t.Union[nodes.Expr, None], + ]: token = self.stream.expect("lparen") args = [] kwargs = [] @@ -950,7 +957,7 @@ class Parser: next(self.stream) name += "." + self.stream.expect("name").value dyn_args = dyn_kwargs = None - kwargs = [] + kwargs: t.List[nodes.Keyword] = [] if self.stream.current.type == "lparen": args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args() elif self.stream.current.type in { diff --git a/src/jinja2/runtime.py b/src/jinja2/runtime.py index 93e21b4..c64999d 100644 --- a/src/jinja2/runtime.py +++ b/src/jinja2/runtime.py @@ -259,7 +259,10 @@ class Context: @internalcode def call( - __self, __obj: t.Callable, *args: t.Any, **kwargs: t.Any # noqa: B902 + __self, # noqa: B902 + __obj: t.Callable[..., t.Any], + *args: t.Any, + **kwargs: t.Any, ) -> t.Union[t.Any, "Undefined"]: """Call the callable with the arguments and keyword arguments provided but inject the active context or environment as first diff --git a/src/jinja2/sandbox.py b/src/jinja2/sandbox.py index 06d7414..153f42e 100644 --- a/src/jinja2/sandbox.py +++ b/src/jinja2/sandbox.py @@ -37,7 +37,7 @@ UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"} #: unsafe attributes on async generators UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"} -_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( +_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = ( ( abc.MutableSet, frozenset( @@ -80,7 +80,7 @@ _mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = ( ) -def inspect_format_method(callable: t.Callable) -> t.Optional[str]: +def inspect_format_method(callable: t.Callable[..., t.Any]) -> t.Optional[str]: if not isinstance( callable, (types.MethodType, types.BuiltinMethodType) ) or callable.__name__ not in ("format", "format_map"): @@ -350,7 +350,7 @@ class SandboxedEnvironment(Environment): s: str, args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any], - format_func: t.Optional[t.Callable] = None, + format_func: t.Optional[t.Callable[..., t.Any]] = None, ) -> str: """If a format call is detected, then this is routed through this method so that our safety sandbox can be used for it. diff --git a/src/jinja2/tests.py b/src/jinja2/tests.py index a467cf0..0d29f94 100644 --- a/src/jinja2/tests.py +++ b/src/jinja2/tests.py @@ -204,7 +204,7 @@ def test_escaped(value: t.Any) -> bool: return hasattr(value, "__html__") -def test_in(value: t.Any, seq: t.Container) -> bool: +def test_in(value: t.Any, seq: t.Container[t.Any]) -> bool: """Check if value is in seq. .. versionadded:: 2.10 diff --git a/src/jinja2/utils.py b/src/jinja2/utils.py index 9b5f5a5..852d082 100644 --- a/src/jinja2/utils.py +++ b/src/jinja2/utils.py @@ -152,7 +152,7 @@ def import_string(import_name: str, silent: bool = False) -> t.Any: raise -def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]: +def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]: """Returns a file descriptor for the filename if that file exists, otherwise ``None``. """ @@ -450,7 +450,9 @@ class LRUCache: self.__dict__.update(d) self._postinit() - def __getnewargs__(self) -> t.Tuple: + def __getnewargs__( + self, + ) -> t.Tuple[int,]: return (self.capacity,) def copy(self) -> "LRUCache": -- cgit v1.2.1