refactor: update project code

This commit is contained in:
2026-07-31 15:52:16 +03:00
parent cc811d2174
commit 9a9338cfdc
16 changed files with 90 additions and 60 deletions
+1
View File
@@ -7,6 +7,7 @@ disable=
too-few-public-methods, too-few-public-methods,
relative-beyond-top-level, relative-beyond-top-level,
subprocess-run-check, subprocess-run-check,
bare-except,
fixme fixme
[FORMAT] [FORMAT]
-1
View File
@@ -1 +0,0 @@
from .common.logging_custom import *
+3 -3
View File
@@ -47,7 +47,7 @@ class CommandLogger(threading.Thread):
class CommandExecutor: class CommandExecutor:
_logger = logger _logger = logger
def __init__(self, command: str) -> None: def __init__(self, command: str):
self._command = command self._command = command
def run(self, basepath: Path): def run(self, basepath: Path):
@@ -58,8 +58,8 @@ class CommandExecutor:
result = subprocess.run( result = subprocess.run(
self._command.split(" "), self._command.split(" "),
cwd=basepath, cwd=basepath,
stdout=out_logger, stdout=out_logger, # type: ignore
stderr=err_logger, stderr=err_logger, # type: ignore
) )
finally: finally:
out_logger.close() out_logger.close()
+9 -9
View File
@@ -1,6 +1,6 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, List, Optional from typing import Iterable
from .command import CommandExecutor from .command import CommandExecutor
@@ -23,18 +23,18 @@ class Entry:
def __init__( def __init__(
self, self,
tasks: Dict[str, List[str]], tasks: dict[str, list[str]],
basepath: Path, basepath: Path,
): ):
self._tasks = tasks self._tasks = tasks
self._basepath = basepath self._basepath = basepath
def show(self): def show(self):
self._logger.info(f"{self}") self._logger.info("%s", self)
for task_name, commands in self._tasks.items(): for task_name, commands in self._tasks.items():
self._logger.info(f" {task_name}") self._logger.info(" %s", task_name)
for command in commands: for command in commands:
self._logger.debug(f" {command}") self._logger.debug(" %s", command)
def run_task(self, task_name: str): def run_task(self, task_name: str):
if task_name == "show": if task_name == "show":
@@ -42,7 +42,7 @@ class Entry:
return return
if task_name not in self._tasks: if task_name not in self._tasks:
raise EntryTaskNotImplementedError(task_name) raise EntryTaskNotImplementedError(task_name)
self._logger.info(f"{task_name} {self}") self._logger.info("%s %s", task_name, self)
for command in self._tasks[task_name]: for command in self._tasks[task_name]:
CommandExecutor(command).run(self._basepath) CommandExecutor(command).run(self._basepath)
@@ -51,12 +51,12 @@ class Entry:
class EntryDetector: class EntryDetector:
def detect_entry(self, basepath: Path) -> Optional["Entry"]: def detect_entry(self, basepath: Path) -> Entry | None:
raise NotImplemented raise NotImplementedError
class EntryResolver: class EntryResolver:
def __init__(self, detector: EntryDetector, ignore: List[str]): def __init__(self, detector: EntryDetector, ignore: list[str]):
self._detector = detector self._detector = detector
self._ignore = ignore self._ignore = ignore
+6 -6
View File
@@ -1,7 +1,7 @@
import logging import logging
from collections import Counter from collections import Counter
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable from typing import Iterable
from .command import CommandError from .command import CommandError
from .entry import Entry, EntryResolver, EntryTaskNotImplementedError from .entry import Entry, EntryResolver, EntryTaskNotImplementedError
@@ -26,20 +26,20 @@ class ProjectManager:
def _iter_entries(self) -> Iterable[Entry]: def _iter_entries(self) -> Iterable[Entry]:
return self._resolver.resolve(self._basepaht) return self._resolver.resolve(self._basepaht)
def run_task(self, task_name: str) -> Dict[str, int]: def run_task(self, task_name: str) -> dict[str, int]:
counter = Counter() counter = Counter()
for entry in self._iter_entries(): for entry in self._iter_entries():
try: try:
entry.run_task(task_name) entry.run_task(task_name)
counter["success"] += 1 counter["success"] += 1
except EntryTaskNotImplementedError: except EntryTaskNotImplementedError:
self._logger.warn(f"{task_name} {entry}") self._logger.warning("%s %s", task_name, entry)
counter["skipped"] += 1 counter["skipped"] += 1
except CommandError as error: except CommandError as error:
self._logger.error(f"{entry}\n{error.output}") self._logger.error("%s\n%s", entry, error.output)
counter["failed"] += 1 counter["failed"] += 1
except Exception: except:
self._logger.exception(entry) self._logger.exception(entry)
counter["failed"] += 1 counter["failed"] += 1
self._logger.info(f"result {dict(counter)}") self._logger.info("result %s", dict(counter))
return counter return counter
+9 -8
View File
@@ -1,7 +1,7 @@
import importlib import importlib
import re import re
from pathlib import Path from pathlib import Path
from typing import Any, Generic, List, Optional, Type, TypeVar from typing import Any, Generic, TypeVar
__all__ = ("Pipe", "PipeRegistry", "PipeParser", "PipeRunner", "PIPE_REGISTRY") __all__ = ("Pipe", "PipeRegistry", "PipeParser", "PipeRunner", "PIPE_REGISTRY")
@@ -12,7 +12,7 @@ O = TypeVar("O")
class Pipe(Generic[I, O]): class Pipe(Generic[I, O]):
type: str type: str
def apply(self, value: I, basepath: Path) -> Optional[O]: def apply(self, value: I, basepath: Path) -> O | None:
raise NotImplementedError raise NotImplementedError
@@ -20,12 +20,13 @@ class PipeRegistry:
def __init__(self): def __init__(self):
self._pipes = {} self._pipes = {}
def register(self, pipe_type: Type[Pipe]): def register(self, pipe_type: type[Pipe]):
self._pipes[pipe_type.type] = pipe_type self._pipes[pipe_type.type] = pipe_type
def register_module(self, module_name: str): def register_module(self, module_name: str):
module = importlib.import_module(module_name) module = importlib.import_module(module_name)
modulepath = Path(module.__file__).parent modulepath = Path(module.__file__).parent
# pylint:disable=too-many-nested-blocks
for item in modulepath.iterdir(): for item in modulepath.iterdir():
if item.suffix == ".py" and not item.name.startswith("__"): if item.suffix == ".py" and not item.name.startswith("__"):
sub_module = importlib.import_module(f"{module_name}.{item.stem}") sub_module = importlib.import_module(f"{module_name}.{item.stem}")
@@ -35,14 +36,14 @@ class PipeRegistry:
if baseclass == Pipe: if baseclass == Pipe:
self.register(v) self.register(v)
def get(self, pipe_type: str) -> Type[Pipe]: def get(self, pipe_type: str) -> type[Pipe]:
return self._pipes[pipe_type] return self._pipes[pipe_type]
class PipeParser: class PipeParser:
_PIPE_PATTERN = re.compile("^(\\w+)\\((.*)\\)") _PIPE_PATTERN = re.compile("^(\\w+)\\((.*)\\)")
def parse(self, pipeline: str) -> List[Pipe]: def parse(self, pipeline: str) -> list[Pipe]:
chain = [item.strip() for item in pipeline.split("|")] chain = [item.strip() for item in pipeline.split("|")]
result = [] result = []
for part in chain: for part in chain:
@@ -59,11 +60,11 @@ class PipeParser:
class PipeRunner: class PipeRunner:
def __init__(self, basepath: Path, parser: Optional[PipeParser] = None): def __init__(self, basepath: Path, parser: PipeParser | None = None):
self._basepath = basepath self._basepath = basepath
self._parser = parser or PipeParser() self._parser = parser or PipeParser()
def run(self, pipes: List[Pipe]) -> Any: def run(self, pipes: list[Pipe]) -> Any:
result = None result = None
for pipe in pipes: for pipe in pipes:
result = pipe.apply(result, self._basepath) result = pipe.apply(result, self._basepath)
@@ -75,7 +76,7 @@ class PipeRunner:
pipe = self._parser.parse(pipeline) pipe = self._parser.parse(pipeline)
return self.run(pipe) return self.run(pipe)
def check_conditions(self, conditions: Optional[List[str]]) -> bool: def check_conditions(self, conditions: list[str] | None) -> bool:
if conditions: if conditions:
for condition in conditions: for condition in conditions:
if not self.resolve_value(condition): if not self.resolve_value(condition):
+5 -4
View File
@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, Optional from typing import Any
from ...util import reflect from ...util import reflect
from ..core import Pipe from ..core import Pipe
@@ -16,7 +16,7 @@ class FileData:
".json": "json:load", ".json": "json:load",
} }
def __init__(self, filepath: Path, loader: Optional[str] = None): def __init__(self, filepath: Path, loader: str | None = None):
self._filepath = filepath self._filepath = filepath
self._loader = loader self._loader = loader
self._data = None self._data = None
@@ -38,6 +38,7 @@ class FileData:
self._load_data() self._load_data()
if self._data: if self._data:
return self._data.get(key) return self._data.get(key)
return None
def __bool__(self): def __bool__(self):
return self._filepath.exists() return self._filepath.exists()
@@ -46,11 +47,11 @@ class FileData:
class FilePipe(Pipe[None, Any]): class FilePipe(Pipe[None, Any]):
type = "file" type = "file"
def __init__(self, filename: Optional[str], loader: Optional[str] = None): def __init__(self, filename: str | None, loader: str | None = None):
self._filename = filename self._filename = filename
self._loader = loader self._loader = loader
def apply(self, value: Optional[str], basepath: Path) -> Optional[Any]: def apply(self, value: str | None, basepath: Path) -> Any | None:
filepath = Path(value or self._filename) # TODO: raise none filepath exception filepath = Path(value or self._filename) # TODO: raise none filepath exception
if not filepath.is_absolute(): if not filepath.is_absolute():
filepath = basepath / filepath filepath = basepath / filepath
+4 -4
View File
@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, Optional from typing import Any
from ..core import Pipe from ..core import Pipe
@@ -12,7 +12,7 @@ class GetPipe(Pipe[dict, Any]):
def __init__(self, keypath: str): def __init__(self, keypath: str):
self._keypath = keypath self._keypath = keypath
def apply(self, value: dict, basepath: Path) -> Optional[Any]: def apply(self, value: dict, basepath: Path) -> Any | None:
path = self._keypath.split(".") path = self._keypath.split(".")
result = value.get(path[0]) result = value.get(path[0])
for key in path[1:]: for key in path[1:]:
@@ -24,9 +24,9 @@ class GetPipe(Pipe[dict, Any]):
class KeyPipe(Pipe[dict, Any]): class KeyPipe(Pipe[dict, Any]):
type = "key" type = "key"
def __init__(self, keyindex: Optional[str]): def __init__(self, keyindex: str | None):
self._keyindex = int(keyindex) if keyindex else 0 self._keyindex = int(keyindex) if keyindex else 0
def apply(self, value: dict, basepath: Path) -> Optional[Any]: def apply(self, value: dict, basepath: Path) -> Any | None:
keys = list(value.keys()) keys = list(value.keys())
return keys[self._keyindex] return keys[self._keyindex]
+2 -2
View File
@@ -1,6 +1,5 @@
import re import re
from pathlib import Path from pathlib import Path
from typing import Optional
from ..core import Pipe from ..core import Pipe
@@ -14,7 +13,8 @@ class SearchPipe(Pipe[str, str]):
self._patter = patter self._patter = patter
self._position = int(position) self._position = int(position)
def apply(self, value: str, basepath: Path) -> Optional[str]: def apply(self, value: str, basepath: Path) -> str | None:
matched = re.compile(self._patter).search(value) matched = re.compile(self._patter).search(value)
if matched: if matched:
return matched.group(self._position) return matched.group(self._position)
return None
+6 -6
View File
@@ -1,6 +1,5 @@
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Optional
from ..entry import Entry, EntryDetector from ..entry import Entry, EntryDetector
from ..pipe.core import PipeParser, PipeRunner from ..pipe.core import PipeParser, PipeRunner
@@ -12,21 +11,21 @@ __all__ = ("PresetEntryDetector",)
class PresetEntryDetector(EntryDetector): class PresetEntryDetector(EntryDetector):
_PIPE_PARSER = PipeParser() _PIPE_PARSER = PipeParser()
def __init__(self, preset_resolver_config: Optional[PresetResolverConfig] = None): def __init__(self, preset_resolver_config: PresetResolverConfig | None = None):
self._pipeparser = PipeParser() self._pipeparser = PipeParser()
self._preset_resolver_config = preset_resolver_config self._preset_resolver_config = preset_resolver_config
def detect_entry(self, basepath: Path) -> Optional["Entry"]: def detect_entry(self, basepath: Path) -> Entry | None:
preset = PresetResolver(basepath, self._preset_resolver_config).resolve_preset() preset = PresetResolver(basepath, self._preset_resolver_config).resolve_preset()
if preset.skip: if preset.skip:
return return None
piperunner = PipeRunner(basepath) piperunner = PipeRunner(basepath)
tasks = defaultdict(list) tasks = defaultdict(list)
for preset_name, preset in preset.presets.items(): for preset in preset.presets.values():
if not piperunner.check_conditions(preset.conditions): if not piperunner.check_conditions(preset.conditions):
continue continue
for task_name, task_pack in preset.tasks.items(): for task_name, task_pack in preset.tasks.items():
for task_item_name, task in task_pack.items(): for task in task_pack.values():
if piperunner.check_conditions(task.conditions): if piperunner.check_conditions(task.conditions):
variables = { variables = {
"entry_path": basepath, "entry_path": basepath,
@@ -43,3 +42,4 @@ class PresetEntryDetector(EntryDetector):
] ]
if tasks: if tasks:
return Entry(tasks, basepath) return Entry(tasks, basepath)
return None
+4 -5
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, Optional from typing import Iterable
import tomli import tomli
@@ -21,16 +21,15 @@ class PresetResolverConfig:
class PresetResolver: class PresetResolver:
_source_cache: Dict[Path, dict] = {} _source_cache: dict[Path, dict] = {}
def __init__(self, basepath: Path, config: Optional[PresetResolverConfig] = None): def __init__(self, basepath: Path, config: PresetResolverConfig | None = None):
self._basepath = basepath self._basepath = basepath
self._config = config or PresetResolverConfig() self._config = config or PresetResolverConfig()
def iter_sources(self) -> Iterable[Path]: def iter_sources(self) -> Iterable[Path]:
if self._config.default: if self._config.default:
for filepath in (Path(__file__).parent / "default").iterdir(): yield from (Path(__file__).parent / "default").iterdir()
yield filepath
if self._config.user: if self._config.user:
user_preset = Path.home() / ".preset.toml" user_preset = Path.home() / ".preset.toml"
if user_preset.exists(): if user_preset.exists():
+3
View File
@@ -29,3 +29,6 @@ requires-poetry = ">=2.0.0,<3.0.0"
[build-system] [build-system]
requires = ["poetry-core (>=2.0.0,<3.0.0)"] requires = ["poetry-core (>=2.0.0,<3.0.0)"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black]
target-version = ["py314"]
+1
View File
@@ -0,0 +1 @@
import project_manager.common.logging_custom
+18 -9
View File
@@ -1,17 +1,26 @@
[tool.poetry] [project]
name = "example" name = "example"
version = "0.1.0"
description = "" description = ""
version = "0.1.0"
authors = [] authors = []
requires-python = ">=3.14,<4.0"
dependencies = []
[tool.poetry.dependencies] [dependency-groups]
python = "^3.8" dev = [
"black (>=26.5.1,<27.0.0)",
"isort (>=8.0.1,<9.0.0)",
"pylint (>=4.0.6,<5.0.0)",
]
test = ["pytest (>=9.1.1,<10.0.0)"]
[tool.poetry.group.dev.dependencies] [tool.poetry]
black = "^23.1.0" package-mode = false
pytest = "^7.2.2" requires-poetry = ">=2.0.0,<3.0.0"
isort = "^5.12.0"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core (>=2.0.0,<3.0.0)"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black]
target-version = ["py314"]
+17
View File
@@ -0,0 +1,17 @@
[tool.poetry]
name = "example"
version = "0.1.0"
description = ""
authors = []
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"
pytest = "^7.2.2"
isort = "^5.12.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+2 -3
View File
@@ -1,7 +1,6 @@
# pylint:disable=redefined-outer-name,protected-access # pylint:disable=redefined-outer-name,protected-access
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Optional
import pytest import pytest
@@ -30,7 +29,7 @@ def entry(entry_detector: EntryDetector, entry_path: Path, tmp_path: Path):
@pytest.mark.parametrize("entry_path", [Path(__file__).parent / "python"]) @pytest.mark.parametrize("entry_path", [Path(__file__).parent / "python"])
def test_python_entry(entry: Optional[Entry], entry_detector: EntryDetector): def test_python_entry(entry: Entry | None, entry_detector: EntryDetector):
assert entry is not None assert entry is not None
assert {"setup", "build"} == entry._tasks.keys() assert {"setup", "build"} == entry._tasks.keys()
entry.run_task("setup") entry.run_task("setup")
@@ -40,7 +39,7 @@ def test_python_entry(entry: Optional[Entry], entry_detector: EntryDetector):
@pytest.mark.parametrize("entry_path", [Path(__file__).parent / "nodejs"]) @pytest.mark.parametrize("entry_path", [Path(__file__).parent / "nodejs"])
def test_nodejs_entry(entry: Optional[Entry], entry_detector: EntryDetector): def test_nodejs_entry(entry: Entry | None, entry_detector: EntryDetector):
assert entry is not None assert entry is not None
assert {"setup", "build"} == entry._tasks.keys() assert {"setup", "build"} == entry._tasks.keys()
entry.run_task("setup") entry.run_task("setup")