complete entrypoint resolver refactor

This commit is contained in:
2026-03-13 17:29:14 +03:00
parent b9b83540e2
commit db94cc8c9e
6 changed files with 77 additions and 83 deletions
+1 -2
View File
@@ -9,8 +9,7 @@ orchestrator = Orchestrator()
def main() -> None: def main() -> None:
app.include_router(router) app.include_router(router)
app.set_description_message_pattern( app.set_description_message_pattern(
lambda command, lambda command, description: f"[bold cyan]▸[/bold cyan] [bold white]{command}[/bold white] [dim]│[/dim] [yellow italic]{description}[/yellow italic]"
description: f"[bold cyan]▸[/bold cyan] [bold white]{command}[/bold white] [dim]│[/dim] [yellow italic]{description}[/yellow italic]"
) )
orchestrator.run_repl(app) orchestrator.run_repl(app)
+1 -1
View File
@@ -5,7 +5,7 @@ from .commands import run_handler, init_handler, new_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 orchestrator is required')(run_handler) app.command("run", help='Command to start the orchestrator repl; the path to the callable object is required')(run_handler)
app.command("init", help="Creates a flat/src boilerplate architecture in an existing project")(init_handler) app.command("init", help="Creates a flat/src boilerplate architecture in an existing project")(init_handler)
app.command("new", help="Creates a project and in it flat/src boilerplate architecture")(new_handler) app.command("new", help="Creates a project and in it flat/src boilerplate architecture")(new_handler)
app() app()
+17 -35
View File
@@ -1,47 +1,29 @@
__all__ = ["run_handler"] __all__ = ["run_handler"]
import importlib
import os import os
import sys
from pathlib import Path from pathlib import Path
from typing import Any import sys
from ..infrastructure.entrypoint_resolver.entity import (
CallableEntryPoint,
EntrypointResolver,
ResolveFromStringError,
)
class ImportFromStringError(Exception): def run_handler(entrypoint_path: str) -> None:
pass
def import_from_string(import_str: str) -> Any:
module_str, _, attrs_str = import_str.partition(":")
if not module_str or not attrs_str:
raise ImportFromStringError(
f'Import string "{import_str}" must be in format "<module>:<attribute>".'
)
try:
module = importlib.import_module(module_str)
except ModuleNotFoundError as exc:
raise ImportFromStringError(f'Could not import module "{module_str}".') from exc
instance = module
try:
for attr_str in attrs_str.split("."):
instance = getattr(instance, attr_str)
except AttributeError:
raise ImportFromStringError(f'Attribute "{attrs_str}" not found in module "{module_str}".')
return instance
def run_handler(entry_point: str) -> None:
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1" os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
if not entrypoint_callable_name:
raise ResolveFromStringError(
"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: if str(Path.cwd()) not in sys.path:
sys.path.insert(0, str(Path.cwd())) sys.path.insert(0, str(Path.cwd()))
runner = import_from_string(entry_point) runner = EntrypointResolver(entrypoint_path).parse_entrypoint_with_type(
entrypoint_callable_name, CallableEntryPoint
)
if not callable(runner): runner.instance_object()
raise TypeError(f'"{entry_point}" is not callable')
runner()
@@ -0,0 +1,7 @@
from .entity import CallableEntryPoint as CallableEntryPoint
from .entity import EntryPointAsApp as EntryPointAsApp
from .entity import EntrypointResolver as EntrypointResolver
from .exceptions import EntrypointNotCallableError as EntrypointNotCallableError
from .exceptions import ResolveFromStringError as ResolveFromStringError
from .exceptions import CallableEntrypointNotMatchRequiredSignatureError as CallableEntrypointNotMatchRequiredSignatureError
from .exceptions import EntrypointNotAppInstanceError as EntrypointNotAppInstanceError
@@ -1,30 +1,19 @@
from dataclasses import dataclass __all__ = ['EntrypointResolver', 'EntryPointAsApp', 'CallableEntryPoint']
import inspect
import importlib.util import importlib.util
import inspect
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Callable, Protocol, cast, overload from typing import Callable, Protocol, cast, overload
from argenta import App from argenta.app.models import App
from .exceptions import (
class ResolverError(Exception): CallableEntrypointNotMatchRequiredSignatureError,
def __init__(self, entrypoint_as_repr: str) -> None: EntrypointNotAppInstanceError,
self.entrypoint_as_repr = entrypoint_as_repr EntrypointNotCallableError,
ResolveFromStringError,
class ResolveFromStringError(ResolverError): )
pass
class EntrypointNotCallableError(ResolverError):
def __str__(self):
return f'Entrypoint {self.entrypoint_as_repr} is not callable'
class CallableEntrypointNotMatchRequiredSignatureError(ResolverError):
def __str__(self) -> str:
return f'Callable entrypoint {self.entrypoint_as_repr} not match with required signature Callable[[], ...]'
class EntrypointNotAppInstanceError(ResolverError):
def __str__(self):
return f'Entrypoint {self.entrypoint_as_repr} is not instance of App'
class EntryPoint[T](Protocol): class EntryPoint[T](Protocol):
@@ -52,21 +41,17 @@ class EntrypointResolver:
@overload @overload
def parse_entrypoint_with_type( def parse_entrypoint_with_type(
self, self, entrypoint_object_name: str, entrypoint_type: type[CallableEntryPoint]
entrypoint_object_name: str,
entrypoint_type: type[CallableEntryPoint]
) -> EntryPoint[Callable[[], None]]: ... ) -> EntryPoint[Callable[[], None]]: ...
@overload @overload
def parse_entrypoint_with_type( def parse_entrypoint_with_type(
self, self, entrypoint_object_name: str, entrypoint_type: type[EntryPointAsApp]
entrypoint_object_name: str,
entrypoint_type: type[EntryPointAsApp]
) -> EntryPoint[App]: ... ) -> EntryPoint[App]: ...
def parse_entrypoint_with_type( def parse_entrypoint_with_type(
self, self,
entrypoint_object_name: str, entrypoint_object_name: str,
entrypoint_type: type[CallableEntryPoint] | type[EntryPointAsApp] entrypoint_type: type[CallableEntryPoint] | type[EntryPointAsApp],
) -> EntryPoint[Callable[[], None]] | EntryPoint[App]: ) -> EntryPoint[Callable[[], None]] | EntryPoint[App]:
if entrypoint_type is CallableEntryPoint: if entrypoint_type is CallableEntryPoint:
return self._parse_callable_entrypoint(entrypoint_object_name) return self._parse_callable_entrypoint(entrypoint_object_name)
@@ -100,11 +85,6 @@ class EntrypointResolver:
file_path: str = self._path_to_entrypoint file_path: str = self._path_to_entrypoint
attr_name: str = entrypoint_object_name attr_name: str = entrypoint_object_name
if not file_path or not attr_name:
raise ResolveFromStringError(
f'"{self._path_to_entrypoint}" must be in format "<path/to/file.py>"'
)
path = Path(file_path).resolve() path = Path(file_path).resolve()
if not path.exists(): if not path.exists():
raise ResolveFromStringError(f'File "{file_path}" not found') raise ResolveFromStringError(f'File "{file_path}" not found')
@@ -0,0 +1,26 @@
class ResolverError(Exception):
pass
class ResolveFromStringError(ResolverError):
pass
class EntrypointError(Exception):
def __init__(self, entrypoint_as_repr: str) -> None:
self.entrypoint_as_repr = entrypoint_as_repr
class EntrypointNotCallableError(EntrypointError):
def __str__(self):
return f"Entrypoint {self.entrypoint_as_repr} is not callable"
class CallableEntrypointNotMatchRequiredSignatureError(EntrypointError):
def __str__(self) -> str:
return f"Callable entrypoint {self.entrypoint_as_repr} not match with required signature Callable[[], ...]"
class EntrypointNotAppInstanceError(EntrypointError):
def __str__(self):
return f"Entrypoint {self.entrypoint_as_repr} is not instance of App"