ReMe/reme_cli/component/base_component.py
jinli.yl 4b5fb37b6a
Some checks are pending
Pre-commit / run (ubuntu-latest) (push) Waiting to run
feat(core): add core components and architecture for ReMe CLI
- Implement BaseComponent with async lifecycle and context management
- Add ApplicationContext for managing component initialization and registry
- Create Application class for orchestrating job execution and lifecycle
- Add AS LLM components with OpenAI chat model wrapper
- Implement AS LLM formatter components with OpenAI formatter
- Add client implementations including base, HTTP and ReMe clients
- Create embedding model base class with caching and batching support
- Implement file store base class with vector and full-text search
- Add file watcher components for monitoring file system changes
- Create job components for executing workflows
- Implement service components for exposing jobs via different protocols
- Add configuration schema with ApplicationConfig and ComponentConfig
- Include utility modules for case conversion, chunking, logging and similarity
- Register component types and create component registry system
2026-04-13 23:51:59 +08:00

213 lines
7.8 KiB
Python

"""Base class for components."""
from abc import ABC, abstractmethod
from ..enumeration import ComponentEnum
from ..utils.logger_utils import get_logger
class BaseComponent(ABC):
"""Base class for all application components.
Provides an asynchronous lifecycle with start/close operations and
async context manager support. State tracking prevents duplicate
start or close calls.
Subclasses must implement ``_start`` and ``_close`` to define their
specific initialization and teardown logic.
Examples:
Direct usage::
comp = MyComponent()
await comp.start()
# ... use component ...
await comp.close()
Context manager usage::
async with MyComponent() as comp:
# ... use component ...
Attributes:
component_type: The type identifier for this component, used during
registry lookup. Defaults to ``ComponentEnum.BASE``.
_is_started: Internal flag indicating whether the component has been
started and not yet closed.
"""
from .application_context import ApplicationContext
component_type = ComponentEnum.BASE
def __init__(self, **kwargs) -> None:
"""Initialize a component instance.
Sets up the component's internal state, binds a structured logger
with the component's class name, and stores any additional keyword
arguments for downstream use by subclasses.
Args:
**kwargs: Arbitrary keyword arguments forwarded to the component
subclass. Typically provided by the registry when the
component is instantiated from configuration.
"""
self.kwargs: dict = dict(kwargs)
self.logger = get_logger()
if hasattr(self.logger, "bind"):
self.logger = self.logger.bind(component=self.__class__.__name__)
self._is_started: bool = False
@abstractmethod
async def _start(self, app_context: ApplicationContext | None = None) -> None:
"""Perform the actual initialization logic for this component.
Subclasses must implement this method to set up resources such as
connections, caches, or background tasks. This method is called
internally by ``start()`` after verifying the component is not
already started.
Args:
app_context: The shared application context that provides access
to other initialized components and the application service.
May be ``None`` if the component does not require cross-component
references.
Raises:
ValueError: If required configuration or dependencies are missing
or invalid.
Exception: Any exception raised during resource acquisition will
propagate to the caller of ``start()``.
"""
@abstractmethod
async def _close(self) -> None:
"""Perform the actual teardown logic for this component.
Subclasses must implement this method to release resources such as
closing connections, flushing buffers, or cancelling background tasks.
This method is called internally by ``close()`` after verifying the
component is in a started state.
Raises:
ValueError: If the component is in an unexpected state during shutdown.
Exception: Any exception raised during resource cleanup will
propagate to the caller of ``close()``.
"""
async def start(self, app_context: ApplicationContext | None = None) -> None:
"""Start the component and transition it to an active state.
This is the public entry point for component initialization. It guards
against duplicate starts by returning immediately if the component is
already running, then delegates to ``_start`` for the subclass-specific
setup.
Args:
app_context: The shared application context to pass to ``_start``.
Raises:
ValueError: If the component configuration is invalid or required
dependencies are unavailable (raised by the subclass ``_start``).
"""
if self._is_started:
return
await self._start(app_context)
self._is_started = True
async def close(self) -> None:
"""Close the component and release its resources.
This is the public entry point for component teardown. It guards
against redundant closes by returning immediately if the component
has not been started or is already closed, then delegates to ``_close``
for the subclass-specific cleanup.
Raises:
ValueError: If the component is in an inconsistent state that
prevents safe shutdown (raised by the subclass ``_close``).
"""
if not self._is_started:
return
try:
await self._close()
finally:
self._is_started = False
async def restart(self, app_context: ApplicationContext | None = None) -> None:
"""Restart the component by closing and then starting it again.
This method safely tears down the component if it is currently running
and reinitialized it. If the component is not started, it will simply
be started.
If either the close or start operation fails, the exception propagates
immediately and the component will be left in a non-started state.
Args:
app_context: The shared application context to pass during startup.
Raises:
ValueError: If the component cannot be cleanly shut down or
reinitialized due to invalid state or configuration.
"""
await self.close()
await self.start(app_context)
@property
def is_started(self) -> bool:
"""Return whether the component is currently in a started state.
Returns:
``True`` if ``start()`` has been called and ``close()`` has not
been called since; ``False`` otherwise.
"""
return self._is_started
async def __aenter__(self) -> "BaseComponent":
"""Enter the async context manager by starting the component.
Returns:
The component instance, allowing it to be bound in an ``async with``
statement.
Raises:
ValueError: If the component fails to start due to invalid
configuration or missing dependencies.
"""
await self.start()
return self
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb,
) -> bool:
"""Exit the async context manager by closing the component.
Any exception raised within the context block is not suppressed.
If both the context block and ``close()`` raise exceptions, the
original exception from the context block is preserved and the
close exception is attached as its ``__cause__`` to maintain
the full error chain.
Args:
exc_type: The exception type if an exception was raised in the
context block, otherwise ``None``.
exc_val: The exception value if an exception was raised.
exc_tb: The traceback if an exception was raised.
Returns:
``False`` to indicate that exceptions should not be suppressed.
"""
if self._is_started:
if exc_val is not None:
try:
await self._close()
except BaseException as close_exc:
raise close_exc from exc_val
finally:
self._is_started = False
else:
await self.close()
return False