mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 10:05:28 +03:00
cli module better
This commit is contained in:
@@ -1,14 +1,33 @@
|
|||||||
from typer import Typer
|
from typer import Typer
|
||||||
|
|
||||||
from .commands import run_handler, init_handler, new_handler
|
from .commands import init_handler, new_handler, run_handler
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
app = Typer()
|
app = Typer()
|
||||||
app.command("run", help='Command to start the orchestrator repl; the path to the callable object is required')(run_handler)
|
app.command(
|
||||||
app.command("init", help="Creates a flat/src boilerplate architecture in an existing project")(init_handler)
|
"run",
|
||||||
app.command("new", help="Creates a project and in it flat/src boilerplate architecture")(new_handler)
|
help="Command to start the orchestrator repl; the path to the callable object is required",
|
||||||
|
short_help="Start the orchestrator REPL",
|
||||||
|
epilog="Example: run app/main.py:main",
|
||||||
|
)(run_handler)
|
||||||
|
|
||||||
|
app.command(
|
||||||
|
"init",
|
||||||
|
help="Creates a flat/src boilerplate architecture in an existing project",
|
||||||
|
short_help="Initialize architecture in existing project",
|
||||||
|
epilog="Make sure you are in the project root before running this command.",
|
||||||
|
)(init_handler)
|
||||||
|
|
||||||
|
app.command(
|
||||||
|
"new",
|
||||||
|
help="Creates a project and in it flat/src boilerplate architecture",
|
||||||
|
short_help="Create a new project with boilerplate",
|
||||||
|
epilog="This will create a new directory with the project structure.",
|
||||||
|
)(new_handler)
|
||||||
|
|
||||||
app()
|
app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
__all__ = ["run_handler"]
|
__all__ = ["run_handler"]
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from ..infrastructure.entrypoint_resolver.entity import (
|
from ..infrastructure.entrypoint_resolver.entity import (
|
||||||
CallableEntryPoint,
|
CallableEntryPoint,
|
||||||
@@ -19,11 +17,8 @@ def run_handler(entrypoint_path: str) -> None:
|
|||||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>"
|
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>"
|
||||||
)
|
)
|
||||||
|
|
||||||
if str(Path.cwd()) not in sys.path:
|
runner = EntrypointResolver[CallableEntryPoint](entrypoint_path).parse_entrypoint_with_type(
|
||||||
sys.path.insert(0, str(Path.cwd()))
|
entrypoint_callable_name
|
||||||
|
|
||||||
runner = EntrypointResolver(entrypoint_path).parse_entrypoint_with_type(
|
|
||||||
entrypoint_callable_name, CallableEntryPoint
|
|
||||||
)
|
)
|
||||||
|
|
||||||
runner.instance_object()
|
runner.instance_object()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
__all__ = ['EntrypointResolver', 'EntryPointAsApp', 'CallableEntryPoint']
|
__all__ = ['EntrypointResolver', 'EntryPointAsApp', 'CallableEntryPoint']
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import inspect
|
import inspect
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Protocol, cast, overload
|
import sys
|
||||||
|
from typing import Callable, Protocol, cast, get_args
|
||||||
|
|
||||||
from argenta.app.models import App
|
from argenta.app.models import App
|
||||||
|
|
||||||
@@ -35,28 +35,18 @@ class EntryPointAsApp:
|
|||||||
instance_object: App
|
instance_object: App
|
||||||
|
|
||||||
|
|
||||||
class EntrypointResolver:
|
class EntrypointResolver[T: (CallableEntryPoint, EntryPointAsApp)]:
|
||||||
def __init__(self, path_to_entrypoint: str):
|
def __init__(self, path_to_entrypoint: str):
|
||||||
self._path_to_entrypoint = path_to_entrypoint
|
self._path_to_entrypoint = path_to_entrypoint
|
||||||
|
|
||||||
@overload
|
|
||||||
def parse_entrypoint_with_type(
|
def parse_entrypoint_with_type(
|
||||||
self, entrypoint_object_name: str, entrypoint_type: type[CallableEntryPoint]
|
self, entrypoint_object_name: str,
|
||||||
) -> EntryPoint[Callable[[], None]]: ...
|
) -> T:
|
||||||
@overload
|
entrypoint_type: type[T] = get_args(self.__orig_class__)[0] # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]
|
||||||
def parse_entrypoint_with_type(
|
|
||||||
self, entrypoint_object_name: str, entrypoint_type: type[EntryPointAsApp]
|
|
||||||
) -> EntryPoint[App]: ...
|
|
||||||
|
|
||||||
def parse_entrypoint_with_type(
|
|
||||||
self,
|
|
||||||
entrypoint_object_name: str,
|
|
||||||
entrypoint_type: type[CallableEntryPoint] | type[EntryPointAsApp],
|
|
||||||
) -> EntryPoint[Callable[[], None]] | EntryPoint[App]:
|
|
||||||
if entrypoint_type is CallableEntryPoint:
|
if entrypoint_type is CallableEntryPoint:
|
||||||
return self._parse_callable_entrypoint(entrypoint_object_name)
|
return cast(T, self._parse_callable_entrypoint(entrypoint_object_name))
|
||||||
elif entrypoint_type is EntryPointAsApp:
|
elif entrypoint_type is EntryPointAsApp:
|
||||||
return self._parse_entrypoint_as_app(entrypoint_object_name)
|
return cast(T, self._parse_entrypoint_as_app(entrypoint_object_name))
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _parse_callable_entrypoint(self, entrypoint_object_name: str) -> CallableEntryPoint:
|
def _parse_callable_entrypoint(self, entrypoint_object_name: str) -> CallableEntryPoint:
|
||||||
@@ -82,23 +72,31 @@ class EntrypointResolver:
|
|||||||
return EntryPointAsApp(raw_path=resolved_entrypoint[0], instance_object=instance_object)
|
return EntryPointAsApp(raw_path=resolved_entrypoint[0], instance_object=instance_object)
|
||||||
|
|
||||||
def _resolve_from_string(self, entrypoint_object_name: str) -> tuple[str, object]:
|
def _resolve_from_string(self, entrypoint_object_name: str) -> tuple[str, object]:
|
||||||
file_path: str = self._path_to_entrypoint
|
abs_path = Path(self._path_to_entrypoint).resolve()
|
||||||
attr_name: str = entrypoint_object_name
|
if not abs_path.exists():
|
||||||
|
raise ResolveFromStringError(f'File "{self._path_to_entrypoint}" not found')
|
||||||
|
|
||||||
path = Path(file_path).resolve()
|
package_root = abs_path.parent
|
||||||
if not path.exists():
|
while (package_root / "__init__.py").exists():
|
||||||
raise ResolveFromStringError(f'File "{file_path}" not found')
|
package_root = package_root.parent
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location(path.stem, path)
|
pkg_root_str = str(package_root)
|
||||||
if spec is None or spec.loader is None:
|
if pkg_root_str not in sys.path:
|
||||||
raise ResolveFromStringError(f'Cannot load module from "{file_path}"')
|
sys.path.insert(0, pkg_root_str)
|
||||||
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
module_name = ".".join(abs_path.relative_to(package_root).with_suffix("").parts)
|
||||||
spec.loader.exec_module(module)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
instance = getattr(module, attr_name)
|
module = importlib.import_module(module_name)
|
||||||
except AttributeError:
|
except ImportError as e:
|
||||||
raise ResolveFromStringError(f'"{attr_name}" not found in "{file_path}"')
|
raise ResolveFromStringError(f'Cannot import module "{module_name}": {e}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = getattr(module, entrypoint_object_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise ResolveFromStringError(
|
||||||
|
f'"{entrypoint_object_name}" not found in "{self._path_to_entrypoint}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
return str(abs_path), instance
|
||||||
|
|
||||||
return file_path, instance
|
|
||||||
|
|||||||
@@ -55,10 +55,15 @@ cli = [
|
|||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "cairosvg" },
|
||||||
{ name = "esbonio" },
|
{ name = "esbonio" },
|
||||||
{ name = "isort" },
|
{ name = "isort" },
|
||||||
|
{ name = "matplotlib" },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
|
{ name = "psutil" },
|
||||||
|
{ name = "py-cpuinfo" },
|
||||||
{ name = "pyfakefs" },
|
{ name = "pyfakefs" },
|
||||||
|
{ name = "pygal" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-cov" },
|
{ name = "pytest-cov" },
|
||||||
{ name = "pytest-mock" },
|
{ name = "pytest-mock" },
|
||||||
@@ -77,20 +82,17 @@ docs = [
|
|||||||
{ name = "sphinx-autobuild" },
|
{ name = "sphinx-autobuild" },
|
||||||
{ name = "sphinx-intl" },
|
{ name = "sphinx-intl" },
|
||||||
]
|
]
|
||||||
in-test = [
|
|
||||||
{ name = "cairosvg" },
|
|
||||||
{ name = "plotext" },
|
|
||||||
{ name = "pygal" },
|
|
||||||
]
|
|
||||||
linters = [
|
linters = [
|
||||||
{ name = "isort" },
|
{ name = "isort" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "wemake-python-styleguide" },
|
{ name = "wemake-python-styleguide" },
|
||||||
]
|
]
|
||||||
metrics = [
|
metrics = [
|
||||||
|
{ name = "cairosvg" },
|
||||||
{ name = "matplotlib" },
|
{ name = "matplotlib" },
|
||||||
{ name = "psutil" },
|
{ name = "psutil" },
|
||||||
{ name = "py-cpuinfo" },
|
{ name = "py-cpuinfo" },
|
||||||
|
{ name = "pygal" },
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "pyfakefs" },
|
{ name = "pyfakefs" },
|
||||||
@@ -114,10 +116,15 @@ provides-extras = ["cli"]
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "cairosvg", specifier = ">=2.8.2" },
|
||||||
{ name = "esbonio", specifier = ">=1.0.0" },
|
{ name = "esbonio", specifier = ">=1.0.0" },
|
||||||
{ name = "isort", specifier = ">=7.0.0" },
|
{ name = "isort", specifier = ">=7.0.0" },
|
||||||
|
{ name = "matplotlib", specifier = ">=3.10.8" },
|
||||||
{ name = "mypy", specifier = ">=1.14.1" },
|
{ name = "mypy", specifier = ">=1.14.1" },
|
||||||
|
{ name = "psutil", specifier = ">=7.2.1" },
|
||||||
|
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
||||||
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
||||||
|
{ name = "pygal", specifier = ">=3.1.0" },
|
||||||
{ name = "pytest", specifier = ">=8.3.2" },
|
{ name = "pytest", specifier = ">=8.3.2" },
|
||||||
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||||
{ name = "pytest-mock", specifier = ">=3.15.1" },
|
{ name = "pytest-mock", specifier = ">=3.15.1" },
|
||||||
@@ -136,20 +143,17 @@ docs = [
|
|||||||
{ name = "sphinx-autobuild", specifier = ">=2025.8.25" },
|
{ name = "sphinx-autobuild", specifier = ">=2025.8.25" },
|
||||||
{ name = "sphinx-intl", specifier = ">=2.3.2" },
|
{ name = "sphinx-intl", specifier = ">=2.3.2" },
|
||||||
]
|
]
|
||||||
in-test = [
|
|
||||||
{ name = "cairosvg", specifier = ">=2.8.2" },
|
|
||||||
{ name = "plotext", specifier = ">=5.3.2" },
|
|
||||||
{ name = "pygal", specifier = ">=3.1.0" },
|
|
||||||
]
|
|
||||||
linters = [
|
linters = [
|
||||||
{ name = "isort", specifier = ">=7.0.0" },
|
{ name = "isort", specifier = ">=7.0.0" },
|
||||||
{ name = "ruff", specifier = ">=0.12.12" },
|
{ name = "ruff", specifier = ">=0.12.12" },
|
||||||
{ name = "wemake-python-styleguide", specifier = ">=0.17.0" },
|
{ name = "wemake-python-styleguide", specifier = ">=0.17.0" },
|
||||||
]
|
]
|
||||||
metrics = [
|
metrics = [
|
||||||
|
{ name = "cairosvg", specifier = ">=2.8.2" },
|
||||||
{ name = "matplotlib", specifier = ">=3.10.8" },
|
{ name = "matplotlib", specifier = ">=3.10.8" },
|
||||||
{ name = "psutil", specifier = ">=7.2.1" },
|
{ name = "psutil", specifier = ">=7.2.1" },
|
||||||
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
{ name = "py-cpuinfo", specifier = ">=9.0.0" },
|
||||||
|
{ name = "pygal", specifier = ">=3.1.0" },
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
{ name = "pyfakefs", specifier = ">=5.5.0" },
|
||||||
@@ -1142,15 +1146,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotext"
|
|
||||||
version = "5.3.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/d7/f75f397af966fe252d0d34ffd3cae765317fce2134f925f95e7d6725d1ce/plotext-5.3.2.tar.gz", hash = "sha256:52d1e932e67c177bf357a3f0fe6ce14d1a96f7f7d5679d7b455b929df517068e", size = 61967, upload-time = "2024-09-24T15:13:37.728Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/1e/12fe7c40cd2099a1f454518754ed229b01beaf3bbb343127f0cc13ce6c22/plotext-5.3.2-py3-none-any.whl", hash = "sha256:394362349c1ddbf319548cfac17ca65e6d5dfc03200c40dfdc0503b3e95a2283", size = 64047, upload-time = "2024-09-24T15:13:36.296Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user