diff --git a/Makefile b/Makefile index 1290aef..aa5940e 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,6 @@ lint: ruff . mypy . vulture whole_app --min-confidence 100 - isort -c . black . --check lint-in-docker: diff --git a/poetry.lock b/poetry.lock index 51f844b..cb91da4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -75,6 +75,52 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.22)"] +[[package]] +name = "black" +version = "23.9.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "caio" version = "0.8.4" @@ -528,6 +574,32 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.3.0" @@ -1088,4 +1160,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f8cd847d402520ab5a5c3617863089d7a8d8c0f4686f4ef00bb2380a966bfc69" +content-hash = "55d41edfafd53c3413d6ff9a163e20b1adf44d8c0d312ccb69c1341cea8ded77" diff --git a/pyproject.toml b/pyproject.toml index 01be656..f7f79e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,27 +32,23 @@ vulture = "*" types-toml = "*" pytest-repeat = "*" ruff = "*" - -[tool.isort] -line_length = 120 -multi_line_output = 3 -include_trailing_comma = true -lines_after_imports = 2 -lexicographical = true -sections = ["FUTURE", "STDLIB", "FIRSTPARTY", "THIRDPARTY", "LOCALFOLDER"] -no_lines_before = ["STDLIB", "THIRDPARTY"] -known_third_party = [] -known_local_folder = ["whole_app"] +black = "*" [tool.black] line-length = 120 [tool.ruff] -fix = true line-length = 120 +fix = true select = ["ALL"] ignore = ["D1", "D203", "D213", "FA102", "I", "ANN101"] +[tool.ruff.isort] +lines-after-imports = 2 +no-lines-before = ["standard-library", "local-folder"] +known-third-party = [] +known-local-folder = ["whole_app"] + [tool.ruff.extend-per-file-ignores] "tests/*.py" = [ "ANN001", diff --git a/scripts/__main__.py b/scripts/__main__.py index 342afe4..0cadbb2 100755 --- a/scripts/__main__.py +++ b/scripts/__main__.py @@ -14,7 +14,12 @@ def _update_dockerhub_readme() -> None: - new_content: str = re.sub(r"\#\# Development.*", r"", README_PATH.read_text(), flags=re.I | re.S).strip() + new_content: str = re.sub( + r"\#\# Development.*", + r"", + README_PATH.read_text(), + flags=re.I | re.S, + ).strip() new_content = replace_tag_in_readme(new_content, parse_last_git_tag()) README_PATH.write_text(new_content + "\n") @@ -23,8 +28,8 @@ def _update_readme() -> None: from whole_app.settings import SETTINGS new_content: str = README_PATH.read_text() - settings_schema: dict = SETTINGS.schema()["properties"] - pack_of_readme_lines: list = [] + settings_schema: dict[str, typing.Any] = SETTINGS.schema()["properties"] + pack_of_readme_lines: list[str] = [] for props in settings_schema.values(): settings_env_key: str = props["env_names"].pop().upper() if "description" not in props: @@ -37,7 +42,11 @@ def _update_readme() -> None: "" if "exclusiveMinimum" not in props else f", allowed values from `{props['exclusiveMinimum'] + 1}`" - + (f"to `{props['exclusiveMaximum'] - 1}`" if "exclusiveMaximum" in props else "") + + ( + f"to `{props['exclusiveMaximum'] - 1}`" + if "exclusiveMaximum" in props + else "" + ) ) pack_of_readme_lines.append( f'`{settings_env_key}` {props["description"].rstrip(".")}. ' diff --git a/scripts/_helpers.py b/scripts/_helpers.py index 4ac496b..0304bd0 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -1,4 +1,3 @@ -"""Helper functions.""" import os import re import shlex @@ -7,7 +6,6 @@ def parse_last_git_tag() -> str: - """Return last git tag (works in CI and on localhost).""" last_tag_from_environment: typing.Final[str | None] = os.getenv("GITHUB_REF_NAME") if last_tag_from_environment is None: git_tags_list: typing.Final = shlex.split( @@ -29,7 +27,6 @@ def parse_last_git_tag() -> str: def replace_tag_in_readme(readme_text: str, new_tag: str) -> str: - """Place new tag in README text.""" return re.sub( r"(xfenix/spellcheck-microservice\:)(\d{1,}\.\d{1,}\.\d{1,})", r"\g<1>" + new_tag, diff --git a/tests/test_dict_views.py b/tests/test_dict_views.py index 5125418..87c20c7 100644 --- a/tests/test_dict_views.py +++ b/tests/test_dict_views.py @@ -1,5 +1,4 @@ # pylint: disable=redefined-outer-name, unspecified-encoding -"""Basic test for views.""" import importlib import typing @@ -103,8 +102,6 @@ def test_dummy_provider_init( class TestVarious: - """Various dict api things.""" - def test_disabled_dictionary_views( self: "TestVarious", monkeypatch: typing.Any, diff --git a/tests/test_misc.py b/tests/test_misc.py index 54d6566..9ce252c 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -38,6 +38,12 @@ def set( # noqa: A003 ) -> typing.Any: """Fake setter for «config» object.""" + def load_config(self: "FakeGunicorn") -> None: + pass + + def load(self: "FakeGunicorn") -> None: + pass + def run(self: "FakeGunicorn", *_: typing.Any, **__: typing.Any) -> typing.Any: self.load_config() self.load() diff --git a/whole_app/__main__.py b/whole_app/__main__.py index 768e767..f4a08c8 100644 --- a/whole_app/__main__.py +++ b/whole_app/__main__.py @@ -11,7 +11,7 @@ # pylint: disable=abstract-method -class GunicornCustomApplication(BaseApplication): +class GunicornCustomApplication(BaseApplication): # type: ignore[misc] def load_config(self: "GunicornCustomApplication") -> None: _options: dict[str, str | int] = { "worker_class": "uvicorn.workers.UvicornWorker", diff --git a/whole_app/auth.py b/whole_app/auth.py index 1b7aa02..bc9f0e8 100644 --- a/whole_app/auth.py +++ b/whole_app/auth.py @@ -1,4 +1,5 @@ import typing + import fastapi from fastapi.security.api_key import APIKeyHeader diff --git a/whole_app/dictionaries/__init__.py b/whole_app/dictionaries/__init__.py index d671a69..05f4a7d 100644 --- a/whole_app/dictionaries/__init__.py +++ b/whole_app/dictionaries/__init__.py @@ -1,19 +1,16 @@ -"""Initalizatior of user dictionaries.""" from loguru import logger -from whole_app import misc_helpers -from whole_app.settings import SETTINGS, StorageProviders - from . import dummy as dummy_storage from . import file as file_storage from . import protocol +from whole_app import misc_helpers +from whole_app.settings import SETTINGS, StorageProviders misc_helpers.init_logger() def init_storage() -> None: - """General storage initializer.""" if SETTINGS.dictionaries_storage_provider == StorageProviders.FILE: file_storage.init_storage() elif SETTINGS.dictionaries_storage_provider == StorageProviders.DUMMY: @@ -24,7 +21,6 @@ def init_storage() -> None: def prepare_storage_engine() -> protocol.UserDictProtocol: - """Storage engine factory.""" if SETTINGS.dictionaries_storage_provider == StorageProviders.FILE: return file_storage.FileProvider() return dummy_storage.DummyProvider() diff --git a/whole_app/dictionaries/file.py b/whole_app/dictionaries/file.py index 4872461..3cc4a2d 100644 --- a/whole_app/dictionaries/file.py +++ b/whole_app/dictionaries/file.py @@ -1,11 +1,9 @@ -"""Basic file provider.""" import aiopath from whole_app.settings import SETTINGS def init_storage() -> None: - """Initialize dictionaries storage helper.""" SETTINGS.dictionaries_path.mkdir( parents=True, exist_ok=True, @@ -13,21 +11,16 @@ def init_storage() -> None: class FileProvider: - """File based storage provider for user dictionaries.""" - _user_dict_path: aiopath.AsyncPath def prepare(self: "FileProvider", user_name: str) -> "FileProvider": - """Prepare class for user name.""" self._user_dict_path = aiopath.AsyncPath(SETTINGS.dictionaries_path / user_name) return self async def _store_lines(self: "FileProvider", lines: list[str]) -> None: - """Store lines to user dictionary.""" await self._user_dict_path.write_text("\n".join(lines) + "\n") async def save_record(self: "FileProvider", exception_word: str) -> None: - """Save record to user dictionary.""" await self._user_dict_path.touch() clean_word: str = exception_word.strip().lower() file_content: list[str] = await self.fetch_records() @@ -36,14 +29,12 @@ async def save_record(self: "FileProvider", exception_word: str) -> None: await self._store_lines(file_content) async def remove_record(self: "FileProvider", exception_word: str) -> None: - """Remove record from user dictionary.""" file_content: list[str] = await self.fetch_records() if exception_word in file_content: file_content.remove(exception_word) await self._store_lines(file_content) async def fetch_records(self: "FileProvider") -> list[str]: - """Fetch records from user dictionary.""" if await self._user_dict_path.exists(): return [ one_line.strip() diff --git a/whole_app/settings.py b/whole_app/settings.py index 26d88b3..854d5ee 100644 --- a/whole_app/settings.py +++ b/whole_app/settings.py @@ -1,10 +1,9 @@ -# pylint: disable=no-member, no-self-argument import enum import pathlib import typing -import toml import pydantic +import toml from loguru import logger from pydantic_settings import BaseSettings @@ -48,7 +47,6 @@ def _warn_about_empty_api_key( def _parse_version_from_local_file( default_value: str, ) -> str: - """Parse version from pyproject (this file will be updated in the CI pipeline).""" try: pyproject_obj: dict[str, dict[str, dict[str, str]]] = toml.loads( PATH_TO_PYPROJECT.read_text(), diff --git a/whole_app/spell.py b/whole_app/spell.py index 888b953..3d6f686 100644 --- a/whole_app/spell.py +++ b/whole_app/spell.py @@ -1,4 +1,3 @@ -"""Spellcheck service functions.""" import typing import pylru @@ -14,8 +13,6 @@ class SpellCheckService: - """Spellcheck service class.""" - __slots__ = ("_input_text", "_spellcheck_engine", "_exclusion_words") _input_text: str @@ -35,7 +32,6 @@ def prepare( @staticmethod def get_memorized_suggestions(word_spellcheck_result: SpellChecker) -> list[str]: - """Try to get suggestions from lru cache or ask SpellChecker for them.""" misspelled_suggestions: list[str] if word_spellcheck_result.word in _MISSPELED_CACHE: misspelled_suggestions = _MISSPELED_CACHE[word_spellcheck_result.word] @@ -49,7 +45,6 @@ def get_memorized_suggestions(word_spellcheck_result: SpellChecker) -> list[str] ) def run_check(self: "SpellCheckService") -> list[models.OneCorrection]: - """Run main checking procedure.""" corrections_output: list[models.OneCorrection] = [] self._spellcheck_engine.set_text(self._input_text) for one_result in self._spellcheck_engine: