From 436d2ffdd5dceef714993f8035a397eaa8d547ef Mon Sep 17 00:00:00 2001 From: Krzysztof Magusiak Date: Sun, 30 Jun 2024 19:54:27 +0200 Subject: [PATCH] Replace flake8 with ruff (#20) * Remplace flake8 by ruff * Update python version to 3.9 and fix code --- .flake8 | 23 ------------------- .gitignore | 3 +-- alphaconf/__init__.py | 5 +++-- alphaconf/cli.py | 3 ++- alphaconf/inject.py | 8 +++---- alphaconf/interactive.py | 3 +-- alphaconf/internal/application.py | 9 ++++---- alphaconf/internal/arg_parser.py | 23 ++++++++++--------- alphaconf/internal/configuration.py | 35 ++++++++++++----------------- alphaconf/internal/load_file.py | 4 ++-- alphaconf/invoke.py | 6 ++--- alphaconf/logging_util.py | 2 +- pre-commit | 11 ++++++--- pyproject.toml | 34 ++++++++++++++++++++++++---- requirements.txt | 3 +-- tests/test_logging.py | 2 +- 16 files changed, 88 insertions(+), 86 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 74877bf..0000000 --- a/.flake8 +++ /dev/null @@ -1,23 +0,0 @@ -[flake8] -# * Complexity -# C901: complexity check (mccabe) -# * Errors & style (https://flake8.pycqa.org/en/latest/user/error-codes.html) -# E123,E133: ignored by default by pep8, so we ignore them here -# E203: space before comma, not pep8 compliant -# E241,E242: spacing after a comma - not used -# F401: module imported but unused -# F812: list comprehension redefines name -# W503: line break before operator - ignored as W504 is in pep8 -# * Documentation (https://pep257.readthedocs.io/en/latest/error_codes.html) -# D100: missing doc in module -# D101: missing doc in class -# D102,D103: missing doc in public method, function -# D205: 1 blank line required between summary line and description -# D210: no whitespaces allowed surrounding docstring text - not used -# D400: first line should end with a period -ignore = C901,E123,E133,E203,F812,W503,D102,D205,D400 -max-line-length = 100 -exclude = .venv,venv,.git -max-complexity = 10 -per-file-ignores = - #**/__init__.py:F401 diff --git a/.gitignore b/.gitignore index 76075c1..18b85f7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,4 @@ __pycache__ dist .ipynb_checkpoints *.egg-info -.mypy_cache -.pytest_cache +.*_cache diff --git a/alphaconf/__init__.py b/alphaconf/__init__.py index c05cc25..76fe177 100644 --- a/alphaconf/__init__.py +++ b/alphaconf/__init__.py @@ -1,8 +1,9 @@ import re import warnings -from typing import Callable, MutableSequence, Optional, Sequence, TypeVar, Union +from collections.abc import MutableSequence, Sequence +from typing import Callable, Optional, TypeVar, Union -from .frozendict import frozendict # noqa: F401 (expose) +from .frozendict import frozendict from .internal.application import Application from .internal.configuration import Configuration diff --git a/alphaconf/cli.py b/alphaconf/cli.py index 8aa674c..23ec7b8 100644 --- a/alphaconf/cli.py +++ b/alphaconf/cli.py @@ -1,5 +1,6 @@ import sys -from typing import Callable, Optional, Sequence, TypeVar, Union +from collections.abc import Sequence +from typing import Callable, Optional, TypeVar, Union from omegaconf import MissingMandatoryValue, OmegaConf diff --git a/alphaconf/inject.py b/alphaconf/inject.py index 7c8e93d..fe4d37b 100644 --- a/alphaconf/inject.py +++ b/alphaconf/inject.py @@ -1,6 +1,6 @@ import functools import inspect -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Optional, Union import alphaconf @@ -12,7 +12,7 @@ class ParamDefaultsFunction: """Function wrapper that injects default parameters""" - _arg_factory: Dict[str, Callable[[], Any]] + _arg_factory: dict[str, Callable[[], Any]] def __init__(self, func: Callable): self.func = func @@ -52,8 +52,8 @@ def getter( ktype = next(type_from_annotation(ptype), None) if param is not None and param.default is not param.empty: xparam = param - return ( - lambda: xparam.default + return lambda: ( + xparam.default if (value := alphaconf.get(key, ktype, default=None)) is None and xparam.default is not xparam.empty else value diff --git a/alphaconf/interactive.py b/alphaconf/interactive.py index 53216a7..0791aeb 100644 --- a/alphaconf/interactive.py +++ b/alphaconf/interactive.py @@ -1,5 +1,4 @@ import logging -from typing import List from . import set_application from .internal.application import Application @@ -11,7 +10,7 @@ application = Application(name="interactive") -def mount(configuration_paths: List[str] = [], setup_logging: bool = True): +def mount(configuration_paths: list[str] = [], setup_logging: bool = True): """Mount the interactive application and setup configuration""" application.setup_configuration(configuration_paths=configuration_paths) set_application(application) diff --git a/alphaconf/internal/application.py b/alphaconf/internal/application.py index b4e3691..04aae10 100644 --- a/alphaconf/internal/application.py +++ b/alphaconf/internal/application.py @@ -3,7 +3,8 @@ import os import sys import uuid -from typing import Callable, Iterable, List, MutableMapping, Optional, Tuple, Union, cast +from collections.abc import Iterable, MutableMapping +from typing import Callable, Optional, Union, cast from omegaconf import DictConfig, OmegaConf @@ -133,7 +134,7 @@ def _get_configurations( self.log.debug('Load configuration from %s', path) yield load_file.read_configuration_file(path) # Environment - prefixes: Optional[Tuple[str, ...]] + prefixes: Optional[tuple[str, ...]] if env_prefixes is True: self.log.debug('Detecting accepted env prefixes') default_keys = {str(k) for k in default_configuration} @@ -155,7 +156,7 @@ def _get_configurations( def setup_configuration( self, *, - arguments: List[str] = [], + arguments: list[str] = [], configuration_paths: Iterable[str] = [], load_dotenv: Optional[bool] = None, env_prefixes: Union[bool, Iterable[str]] = True, @@ -198,7 +199,7 @@ def masked_configuration( *, mask_base: bool = True, mask_secrets: bool = True, - mask_keys: List[str] = ['application.uuid'], + mask_keys: list[str] = ['application.uuid'], ) -> dict: """Get the configuration as dict with masked items diff --git a/alphaconf/internal/arg_parser.py b/alphaconf/internal/arg_parser.py index c140248..b810f04 100644 --- a/alphaconf/internal/arg_parser.py +++ b/alphaconf/internal/arg_parser.py @@ -1,10 +1,11 @@ import itertools -from typing import Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union, cast +from collections.abc import Iterable, Mapping +from typing import Optional, Union, cast from omegaconf import DictConfig, OmegaConf -def _split(value: str, char: str = "=") -> Tuple[str, Optional[str]]: +def _split(value: str, char: str = "=") -> tuple[str, Optional[str]]: vs = value.split(char, 1) if len(vs) < 2: return vs[0], None @@ -91,7 +92,7 @@ class ConfigurationAction(Action): def check_argument(self, value): if self.metavar and '=' in self.metavar and '=' not in value: - return 'Argument should be in format %s' % self.metavar + return f'Argument should be in format {self.metavar}' return super().check_argument(value) def handle(self, result, value): @@ -119,7 +120,7 @@ def check_argument(self, value): def handle(self, result, value): key, value = _split(value) value = value or 'default' - arg = "{key}=${{oc.select:base.{key}.{value}}}".format(key=key, value=value) + arg = f"{key}=${{oc.select:base.{key}.{value}}}" return super().handle(result, arg) @@ -127,8 +128,8 @@ class ParseResult: """The result of argument parsing""" result: Optional[Action] - rest: List[str] - _config: List[Union[str, DictConfig]] + rest: list[str] + _config: list[Union[str, DictConfig]] def __init__(self) -> None: """Initialize the result""" @@ -136,7 +137,7 @@ def __init__(self) -> None: self.rest = [] self._config = [] - def _add_config(self, value: Union[List[str], DictConfig, Dict, str]): + def _add_config(self, value: Union[list[str], DictConfig, dict, str]): """Add a configuration item""" if isinstance(value, list): self._config.extend(value) @@ -169,8 +170,8 @@ def __repr__(self) -> str: class ArgumentParser: """Parses arguments for alphaconf""" - _opt_actions: Dict[str, Action] - _pos_actions: List[Action] + _opt_actions: dict[str, Action] + _pos_actions: list[Action] help_messages: Mapping[str, str] def __init__(self, help_messages: Mapping[str, str] = {}) -> None: @@ -178,7 +179,7 @@ def __init__(self, help_messages: Mapping[str, str] = {}) -> None: self._pos_actions = [] self.help_messages = help_messages or {} - def parse_args(self, arguments: List[str]) -> ParseResult: + def parse_args(self, arguments: list[str]) -> ParseResult: """Parse the argument""" result = ParseResult() arguments = list(arguments) @@ -229,7 +230,7 @@ def parse_args(self, arguments: List[str]) -> ParseResult: result.rest += arguments return result - def add_argument(self, action_class: Type[Action], *names: str, **kw): + def add_argument(self, action_class: type[Action], *names: str, **kw): """Add an argument handler :param action_class: Action(kw) will be added as a handler diff --git a/alphaconf/internal/configuration.py b/alphaconf/internal/configuration.py index 083effd..9a8c6f5 100644 --- a/alphaconf/internal/configuration.py +++ b/alphaconf/internal/configuration.py @@ -1,15 +1,11 @@ import copy import os import warnings +from collections.abc import Iterable, MutableMapping from enum import Enum from typing import ( Any, - Dict, - Iterable, - List, - MutableMapping, Optional, - Type, TypeVar, Union, cast, @@ -33,9 +29,9 @@ class RaiseOnMissingType(Enum): class Configuration: c: DictConfig - __type_path: MutableMapping[Type, Optional[str]] - __type_value: MutableMapping[Type, Any] - helpers: Dict[str, str] + __type_path: MutableMapping[type, Optional[str]] + __type_value: MutableMapping[type, Any] + helpers: dict[str, str] def __init__(self, *, parent: Optional["Configuration"] = None) -> None: if parent: @@ -52,11 +48,10 @@ def __init__(self, *, parent: Optional["Configuration"] = None) -> None: def get( self, key: str, - type: Type[T], + type: type[T], *, default: Union[T, RaiseOnMissingType] = raise_on_missing, - ) -> T: - ... + ) -> T: ... @overload def get( @@ -65,20 +60,18 @@ def get( type: Union[str, None] = None, *, default: Any = raise_on_missing, - ) -> Any: - ... + ) -> Any: ... @overload def get( self, - key: Type[T], + key: type[T], type: None = None, *, default: Union[T, RaiseOnMissingType] = raise_on_missing, - ) -> T: - ... + ) -> T: ... - def get(self, key: Union[str, Type], type=None, *, default=raise_on_missing): + def get(self, key: Union[str, type], type=None, *, default=raise_on_missing): """Get a configuation value and cast to the correct type""" if isinstance(key, _cla_type): return self.__get_type(key, default=default) @@ -102,7 +95,7 @@ def get(self, key: Union[str, Type], type=None, *, default=raise_on_missing): value = convert_to_type(value, type) return value - def __get_type(self, key: Type, *, default=raise_on_missing): + def __get_type(self, key: type, *, default=raise_on_missing): value = self.__type_value.get(key) if value is not None: return value @@ -127,7 +120,7 @@ def _merge(self, configs: Iterable[DictConfig]): def setup_configuration( self, conf: Union[DictConfig, dict, Any], - helpers: Dict[str, str] = {}, + helpers: dict[str, str] = {}, *, prefix: str = "", ): @@ -196,7 +189,7 @@ def from_environ(self, prefixes: Iterable[str]) -> DictConfig: return conf @staticmethod - def _find_name(parts: List[str], conf: DictConfig) -> str: + def _find_name(parts: list[str], conf: DictConfig) -> str: """Find a name from parts, by trying joining with '.' (default) or '_'""" if len(parts) < 2: return "".join(parts) @@ -205,7 +198,7 @@ def _find_name(parts: List[str], conf: DictConfig) -> str: if name: name += "_" name += part - if name in conf.keys(): + if name in conf: sub_conf = conf.get(name) if next_offset == len(parts): return name diff --git a/alphaconf/internal/load_file.py b/alphaconf/internal/load_file.py index f1e89d3..e34748f 100644 --- a/alphaconf/internal/load_file.py +++ b/alphaconf/internal/load_file.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Tuple +from typing import Any from omegaconf import DictConfig, OmegaConf @@ -11,7 +11,7 @@ class TomlDecoderPrimitive(toml.TomlDecoder): """toml loader which reads dates as strings for compitability with JSON""" - def load_value(self, v: str, strictly_valid: bool = True) -> Tuple[Any, str]: + def load_value(self, v: str, strictly_valid: bool = True) -> tuple[Any, str]: value, itype = super().load_value(v, strictly_valid) # convert date, datetime, time using isoformat() if itype in ('date', 'time'): diff --git a/alphaconf/invoke.py b/alphaconf/invoke.py index ccf1e73..5d2561a 100644 --- a/alphaconf/invoke.py +++ b/alphaconf/invoke.py @@ -1,4 +1,4 @@ -from typing import Dict, Union +from typing import Union import invoke from omegaconf import OmegaConf @@ -52,13 +52,13 @@ def run_program(self): return prog.run(argv) -def collection(variables: Dict = {}) -> invoke.Collection: +def collection(variables: dict = {}) -> invoke.Collection: """Create a new collection base on tasks in the variables""" return invoke.Collection(*[v for v in variables.values() if isinstance(v, invoke.Task)]) def run( - __name__: str, namespace: Union[invoke.collection.Collection, Dict], **properties + __name__: str, namespace: Union[invoke.collection.Collection, dict], **properties ) -> InvokeApplication: """Create an invoke application and run it if __name__ is __main__""" if isinstance(namespace, invoke.Collection): diff --git a/alphaconf/logging_util.py b/alphaconf/logging_util.py index 524fb33..a9d567c 100644 --- a/alphaconf/logging_util.py +++ b/alphaconf/logging_util.py @@ -100,7 +100,7 @@ def __init__(self, *args, **kw): def getMessage(self) -> str: # noqa: N802 msg = super().getMessage() if self.context: - msg = "%s %s" % (self.context, msg) + msg = f"{self.context} {msg}" return msg def getRawMessage(self) -> str: # noqa: N802 diff --git a/pre-commit b/pre-commit index a8903c1..8f39d4f 100755 --- a/pre-commit +++ b/pre-commit @@ -6,15 +6,17 @@ cd "$(dirname "$0")" [ -d .git ] pre_commit() { - flake8 + ruff check . black --check . - isort --check-only . mypy . } +fix_all() { + black . + ruff check --fix-only . +} format() { black . - isort . } # Commands @@ -23,6 +25,9 @@ case "${1:-run}" in pre_commit echo "All good to commit" ;; + fix) + fix_all + ;; format) format ;; diff --git a/pyproject.toml b/pyproject.toml index b9a7e56..c2dc939 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,12 +46,38 @@ skip-string-normalization = 1 [tool.mypy] ignore_missing_imports = true -[tool.isort] -profile = "black" -line_length = 100 - [tools.setuptools] packages = ["alphaconf"] [tool.setuptools_scm] local_scheme = "no-local-version" + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +# https://beta.ruff.rs/docs/rules/ +select = [ + #"C9", # mccabe + #"D", # documentation + "E", + "F", + "W", + "I", # isort + "N", # naming + "UP", # pyupdate + "C4", # comprehensions + "EXE", + "SIM", + "RUF", +] +ignore = [ + "D102", # mission doc in public method, function + "D205", # blank line required betweeen summary and description + "D400", # first line should end with a period + "E731", # don't assign lambda + "SIM108", # simplify ITE by operator +] + +[tool.ruff.lint.mccabe] +max-complexity = 10 diff --git a/requirements.txt b/requirements.txt index a0fcc0b..f2a92ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,8 @@ setuptools setuptools_scm black -flake8 -isort mypy +ruff pytest types-toml diff --git a/tests/test_logging.py b/tests/test_logging.py index ddddf8b..9538031 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -40,7 +40,7 @@ def test_log_exception(log, caplog): log.error('err', exc_info=True) rec = caplog.records[0] exc_type, exc, tb = rec.exc_info - assert exc_type == ValueError and str(exc) == 'tvalue' and tb + assert exc_type is ValueError and str(exc) == 'tvalue' and tb def test_log_format(log, caplog):