From 803b94d0444fb973e820b6cdc8bbac704c863e60 Mon Sep 17 00:00:00 2001 From: seria Date: Thu, 28 Mar 2024 23:34:44 +0900 Subject: [PATCH] Migrate to ruff (#168) --- .flake8 | 43 -------------- genshin-dev/lint-requirements.txt | 14 +---- genshin-dev/reformat-requirements.txt | 4 +- genshin/models/genshin/chronicle/abyss.py | 8 +-- genshin/models/genshin/chronicle/stats.py | 2 +- genshin/models/genshin/lineup.py | 2 +- genshin/models/honkai/chronicle/stats.py | 5 +- genshin/utility/extdb.py | 5 +- noxfile.py | 9 ++- pyproject.toml | 68 ++++++++++++++++++++++- 10 files changed, 85 insertions(+), 75 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index eb1a7f74..00000000 --- a/.flake8 +++ /dev/null @@ -1,43 +0,0 @@ -[flake8] -exclude = tests, test.py - -# A001, A002, A003: `id` variable/parameter/attribute -# C408: dict() with keyword arguments -# D105: Missing docstring in magic method -# D106: Missing docstring Model.Config -# D419: Docstring is empty -# E704: Multiple statements on one line (def) -# S101: Use of assert for type checking -# S303: Use of md5 -# S311: Use of pseudo-random generators -# S324: Use of md5 without usedforsecurity=False (3.9+) -# W503: line break before binary operator -ignore = - A001, A002, A003, - C408, - D105, D106, D419, - E704, - S101, S303, S311, S324, - W503, - -# F401: unused import. -# F403: cannot detect unused vars if we use starred import -# D10*: docstrings -# S10*: hardcoded passwords -# F841: unused variable -# I900: nox is a dev dependency -per-file-ignores = - **/__init__.py: F401, F403 - tests/**: D10, S10, F841 - noxfile.py: I900 - -max-complexity = 16 -max-function-length = 100 -max-line-length = 130 - -max_annotations_complexity = 5 - -accept-encodings = utf-8 -docstring-convention = numpy -ignore-decorators = property -requirements_file = requirements.txt \ No newline at end of file diff --git a/genshin-dev/lint-requirements.txt b/genshin-dev/lint-requirements.txt index 4073601f..ede3eb4e 100644 --- a/genshin-dev/lint-requirements.txt +++ b/genshin-dev/lint-requirements.txt @@ -1,13 +1 @@ -flake8 - -flake8-annotations-complexity # complex annotation -flake8-black # runs black -flake8-builtins # builtin shadowing -flake8-docstrings # proper formatting and grammar in docstrings -flake8-isort # runs isort -flake8-mutable # mutable default argument detection -flake8-pep3101 # new-style format strings only -flake8-print # complain about print statements in code -flake8-pytest-style # pytest checks -flake8-raise # exception raising -flake8-requirements # requirements.txt check +ruff \ No newline at end of file diff --git a/genshin-dev/reformat-requirements.txt b/genshin-dev/reformat-requirements.txt index bee49858..1cf5c13f 100644 --- a/genshin-dev/reformat-requirements.txt +++ b/genshin-dev/reformat-requirements.txt @@ -1,3 +1,3 @@ black -isort -sort-all +ruff +sort-all \ No newline at end of file diff --git a/genshin/models/genshin/chronicle/abyss.py b/genshin/models/genshin/chronicle/abyss.py index 829fcd7d..159c2f8f 100644 --- a/genshin/models/genshin/chronicle/abyss.py +++ b/genshin/models/genshin/chronicle/abyss.py @@ -47,13 +47,13 @@ class CharacterRanks(APIModel): most_played: typing.Sequence[AbyssRankCharacter] = Aliased("reveal_rank", default=[], mi18n="bbs/go_fight_count") most_kills: typing.Sequence[AbyssRankCharacter] = Aliased("defeat_rank", default=[], mi18n="bbs/max_rout_count") strongest_strike: typing.Sequence[AbyssRankCharacter] = Aliased("damage_rank", default=[], mi18n="bbs/powerful_attack") - most_damage_taken: typing.Sequence[AbyssRankCharacter] = Aliased("take_damage_rank", default=[], mi18n="bbs/receive_max_damage") - most_bursts_used: typing.Sequence[AbyssRankCharacter] = Aliased("energy_skill_rank", default=[], mi18n="bbs/element_break_count") - most_skills_used: typing.Sequence[AbyssRankCharacter] = Aliased("normal_skill_rank", default=[], mi18n="bbs/element_skill_use_count") + most_damage_taken: typing.Sequence[AbyssRankCharacter] = Aliased("take_damage_rank", default=[], mi18n="bbs/receive_max_damage") # noqa: E501 + most_bursts_used: typing.Sequence[AbyssRankCharacter] = Aliased("energy_skill_rank", default=[], mi18n="bbs/element_break_count") # noqa: E501 + most_skills_used: typing.Sequence[AbyssRankCharacter] = Aliased("normal_skill_rank", default=[], mi18n="bbs/element_skill_use_count") # noqa: E501 # fmt: on def as_dict(self, lang: typing.Optional[str] = None) -> typing.Mapping[str, typing.Any]: - """Helper function which turns fields into properly named ones""" + """Turn fields into properly named ones.""" return { self._get_mi18n(field, lang or self.lang): getattr(self, field.name) for field in self.__fields__.values() diff --git a/genshin/models/genshin/chronicle/stats.py b/genshin/models/genshin/chronicle/stats.py index fb4d9e75..0734a886 100644 --- a/genshin/models/genshin/chronicle/stats.py +++ b/genshin/models/genshin/chronicle/stats.py @@ -53,7 +53,7 @@ class Stats(APIModel): # fmt: on def as_dict(self, lang: typing.Optional[str] = None) -> typing.Mapping[str, typing.Any]: - """Helper function which turns fields into properly named ones""" + """Turn fields into properly named ones.""" return { self._get_mi18n(field, lang or self.lang): getattr(self, field.name) for field in self.__fields__.values() diff --git a/genshin/models/genshin/lineup.py b/genshin/models/genshin/lineup.py index 6ae71946..76f3865e 100644 --- a/genshin/models/genshin/lineup.py +++ b/genshin/models/genshin/lineup.py @@ -296,7 +296,7 @@ def __parse_characters(cls, value: typing.Any) -> typing.Any: if isinstance(value[0], typing.Sequence): return value - return [[character for character in group["group"]] for group in value] + return [list(group["group"]) for group in value] class Lineup(LineupPreview): diff --git a/genshin/models/honkai/chronicle/stats.py b/genshin/models/honkai/chronicle/stats.py index b7f57490..1bb6745d 100644 --- a/genshin/models/honkai/chronicle/stats.py +++ b/genshin/models/honkai/chronicle/stats.py @@ -24,7 +24,7 @@ def _model_to_dict(model: APIModel, lang: str = "en-us") -> typing.Mapping[str, typing.Any]: - """Helper function which turns fields into properly named ones""" + """Turn fields into properly named ones.""" ret: typing.Dict[str, typing.Any] = {} for field in model.__fields__.values(): if not field.field_info.extra.get("mi18n"): @@ -123,7 +123,7 @@ class OldAbyssStats(APIModel): raw_tier: int = Aliased("latest_area", mi18n="bbs/settled_level") raw_latest_rank: typing.Optional[int] = Aliased("latest_level", mi18n="bbs/rank") # TODO: Add proper key - latest_type: str = Aliased( mi18n="bbs/latest_type") + latest_type: str = Aliased( mi18n="bbs/latest_type") # fmt: on @pydantic.validator("raw_q_singularis_rank", "raw_dirac_sea_rank", "raw_latest_rank", pre=True) @@ -240,6 +240,7 @@ def __pack_gamemode_stats(cls, values: typing.Dict[str, typing.Any]) -> typing.D return values def as_dict(self, lang: str = "en-us") -> typing.Mapping[str, typing.Any]: + """Turn fields into properly named ones.""" return _model_to_dict(self, lang) diff --git a/genshin/utility/extdb.py b/genshin/utility/extdb.py index 52fb5ac6..8225d504 100644 --- a/genshin/utility/extdb.py +++ b/genshin/utility/extdb.py @@ -2,6 +2,7 @@ import asyncio import json +import logging import time import typing import warnings @@ -20,6 +21,8 @@ "update_characters_genshindata", ) +LOGGER_ = logging.getLogger(__name__) + CACHE_FILE = fs.get_tempdir() / "characters.json" if CACHE_FILE.exists() and time.time() - CACHE_FILE.stat().st_mtime < 7 * 24 * 60 * 60: @@ -235,7 +238,7 @@ async def update_characters_any( try: await updator(langs) except Exception: - continue + LOGGER_.exception("Failed to update characters with %s", updator.__name__) else: return diff --git a/noxfile.py b/noxfile.py index 550a0401..3da55b37 100644 --- a/noxfile.py +++ b/noxfile.py @@ -31,7 +31,7 @@ def install_requirements(session: nox.Session, *requirements: str, literal: bool """Install requirements.""" if not literal and all(requirement.isalpha() for requirement in requirements): files = ["requirements.txt"] + [f"./genshin-dev/{requirement}-requirements.txt" for requirement in requirements] - requirements = ("pip",) + tuple(arg for file in files for arg in ("-r", file)) + requirements = ("pip", *tuple(arg for file in files for arg in ("-r", file))) session.install("--upgrade", *requirements, silent=not isverbose()) @@ -52,16 +52,15 @@ def docs(session: nox.Session) -> None: def lint(session: nox.Session) -> None: """Run this project's modules against the pre-defined flake8 linters.""" install_requirements(session, "lint") - session.run("flake8", "--version") - session.run("flake8", *GENERAL_TARGETS, *verbose_args()) + session.run("ruff", "check", *GENERAL_TARGETS, *verbose_args()) @nox.session() def reformat(session: nox.Session) -> None: """Reformat this project's modules to fit the standard style.""" install_requirements(session, "reformat") - session.run("black", *GENERAL_TARGETS, *verbose_args()) - session.run("isort", *GENERAL_TARGETS, *verbose_args()) + session.run("python", "-m", "black", *GENERAL_TARGETS, *verbose_args()) + session.run("python", "-m", "ruff", "check", "--fix-only", "--fixable", "ALL", *GENERAL_TARGETS, *verbose_args()) session.log("sort-all") LOGGER.disabled = True diff --git a/pyproject.toml b/pyproject.toml index 804aafe2..96634f51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,72 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 120 -target-version = ["py39"] -[tool.isort] -profile = "black" +[tool.ruff] +line-length = 120 +target-version = "py38" + +[tool.ruff.lint] +select = [ + "A", + "C4", + "C9", + "D", + "E", + "F", + "S", + "W", + "T20", + "PT", + "RSE" +] +exclude = ["tests", "test.py"] + +# A001, A002, A003: `id` variable/parameter/attribute +# C408: dict() with keyword arguments +# D101: Missing docstring in public module +# D105: Missing docstring in magic method +# D106: Missing docstring Model.Config +# D400: First line should end with a period +# D419: Docstring is empty +# PT007: Wrong values type in `@pytest.mark.parametrize` expected `list` of `tuple` +# PT018: Assertion should be broken down into multiple parts +# S101: Use of assert for type checking +# S303: Use of md5 +# S311: Use of pseudo-random generators +# S324: Use of md5 without usedforsecurity=False (3.9+) +ignore = [ + "A001", "A002", "A003", + "C408", + "D100", "D105", "D106", "D400", "D419", + "PT007", "PT018", + "S101", "S303", "S311", "S324", +] + +# auto-fixing too intrusive +# F401: Unused import +# F841: Unused variable +# B007: Unused loop variable +unfixable = ["F401", "F841", "B007"] + +[tool.ruff.lint.per-file-ignores] +# F401: unused import. +# F403: cannot detect unused vars if we use starred import +# D10*: docstrings +# S10*: hardcoded passwords +# F841: unused variable +"**/__init__.py" = ["F401", "F403"] +"tests/**" = ["D10", "S10", "F841"] + +[tool.ruff.lint.mccabe] +max-complexity = 16 + +[tool.ruff.lint.pycodestyle] +max-line-length = 130 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" +ignore-decorators = ["property"] [tool.pytest.ini_options] asyncio_mode = "auto"