feat: add psagentai agent with deployment and configuration
Initial project setup including the main agent script, configuration files, profile JSON state, systemd service unit, startup script, deployment documentation, and Python dependencies. Includes .env, venv/, and __pycache__/ which should likely be excluded via .gitignore to avoid committing secrets and artifacts.
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
"""Plugin interface for Pydantic plugins, and related types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Literal, NamedTuple, TypeAlias
|
||||
|
||||
from pydantic_core import CoreConfig, CoreSchema, ValidationError
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from pydantic.config import ExtraValues
|
||||
|
||||
__all__ = (
|
||||
'PydanticPluginProtocol',
|
||||
'BaseValidateHandlerProtocol',
|
||||
'ValidatePythonHandlerProtocol',
|
||||
'ValidateJsonHandlerProtocol',
|
||||
'ValidateStringsHandlerProtocol',
|
||||
'NewSchemaReturns',
|
||||
'SchemaTypePath',
|
||||
'SchemaKind',
|
||||
)
|
||||
|
||||
NewSchemaReturns: TypeAlias = 'tuple[ValidatePythonHandlerProtocol | None, ValidateJsonHandlerProtocol | None, ValidateStringsHandlerProtocol | None]'
|
||||
|
||||
|
||||
class SchemaTypePath(NamedTuple):
|
||||
"""Path defining where `schema_type` was defined, or where `TypeAdapter` was called."""
|
||||
|
||||
module: str
|
||||
name: str
|
||||
|
||||
|
||||
SchemaKind: TypeAlias = Literal['BaseModel', 'TypeAdapter', 'dataclass', 'create_model', 'validate_call']
|
||||
|
||||
|
||||
class PydanticPluginProtocol(Protocol):
|
||||
"""Protocol defining the interface for Pydantic plugins."""
|
||||
|
||||
def new_schema_validator(
|
||||
self,
|
||||
schema: CoreSchema,
|
||||
schema_type: Any,
|
||||
schema_type_path: SchemaTypePath,
|
||||
schema_kind: SchemaKind,
|
||||
config: CoreConfig | None,
|
||||
plugin_settings: dict[str, object],
|
||||
) -> tuple[
|
||||
ValidatePythonHandlerProtocol | None, ValidateJsonHandlerProtocol | None, ValidateStringsHandlerProtocol | None
|
||||
]:
|
||||
"""This method is called for each plugin every time a new [`SchemaValidator`][pydantic_core.SchemaValidator]
|
||||
is created.
|
||||
|
||||
It should return an event handler for each of the three validation methods, or `None` if the plugin does not
|
||||
implement that method.
|
||||
|
||||
Args:
|
||||
schema: The schema to validate against.
|
||||
schema_type: The original type which the schema was created from, e.g. the model class.
|
||||
schema_type_path: Path defining where `schema_type` was defined, or where `TypeAdapter` was called.
|
||||
schema_kind: The kind of schema to validate against.
|
||||
config: The config to use for validation.
|
||||
plugin_settings: Any plugin settings.
|
||||
|
||||
Returns:
|
||||
A tuple of optional event handlers for each of the three validation methods -
|
||||
`validate_python`, `validate_json`, `validate_strings`.
|
||||
"""
|
||||
raise NotImplementedError('Pydantic plugins should implement `new_schema_validator`.')
|
||||
|
||||
|
||||
class BaseValidateHandlerProtocol(Protocol):
|
||||
"""Base class for plugin callbacks protocols.
|
||||
|
||||
You shouldn't implement this protocol directly, instead use one of the subclasses with adds the correctly
|
||||
typed `on_error` method.
|
||||
"""
|
||||
|
||||
on_enter: Callable[..., None]
|
||||
"""`on_enter` is changed to be more specific on all subclasses"""
|
||||
|
||||
def on_success(self, result: Any) -> None:
|
||||
"""Callback to be notified of successful validation.
|
||||
|
||||
Args:
|
||||
result: The result of the validation.
|
||||
"""
|
||||
return
|
||||
|
||||
def on_error(self, error: ValidationError) -> None:
|
||||
"""Callback to be notified of validation errors.
|
||||
|
||||
Args:
|
||||
error: The validation error.
|
||||
"""
|
||||
return
|
||||
|
||||
def on_exception(self, exception: Exception) -> None:
|
||||
"""Callback to be notified of validation exceptions.
|
||||
|
||||
Args:
|
||||
exception: The exception raised during validation.
|
||||
"""
|
||||
return
|
||||
|
||||
|
||||
class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
|
||||
"""Event handler for `SchemaValidator.validate_python`."""
|
||||
|
||||
def on_enter(
|
||||
self,
|
||||
input: Any,
|
||||
*,
|
||||
strict: bool | None = None,
|
||||
extra: ExtraValues | None = None,
|
||||
from_attributes: bool | None = None,
|
||||
context: Any | None = None,
|
||||
self_instance: Any | None = None,
|
||||
by_alias: bool | None = None,
|
||||
by_name: bool | None = None,
|
||||
) -> None:
|
||||
"""Callback to be notified of validation start, and create an instance of the event handler.
|
||||
|
||||
Args:
|
||||
input: The input to be validated.
|
||||
strict: Whether to validate the object in strict mode.
|
||||
extra: Whether to ignore, allow, or forbid extra data during model validation.
|
||||
from_attributes: Whether to validate objects as inputs by extracting attributes.
|
||||
context: The context to use for validation, this is passed to functional validators.
|
||||
self_instance: An instance of a model to set attributes on from validation, this is used when running
|
||||
validation from the `__init__` method of a model.
|
||||
by_alias: Whether to use the field's alias to match the input data to an attribute.
|
||||
by_name: Whether to use the field's name to match the input data to an attribute.
|
||||
"""
|
||||
|
||||
|
||||
class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
|
||||
"""Event handler for `SchemaValidator.validate_json`."""
|
||||
|
||||
def on_enter(
|
||||
self,
|
||||
input: str | bytes | bytearray,
|
||||
*,
|
||||
strict: bool | None = None,
|
||||
extra: ExtraValues | None = None,
|
||||
context: Any | None = None,
|
||||
self_instance: Any | None = None,
|
||||
by_alias: bool | None = None,
|
||||
by_name: bool | None = None,
|
||||
) -> None:
|
||||
"""Callback to be notified of validation start, and create an instance of the event handler.
|
||||
|
||||
Args:
|
||||
input: The JSON data to be validated.
|
||||
strict: Whether to validate the object in strict mode.
|
||||
extra: Whether to ignore, allow, or forbid extra data during model validation.
|
||||
context: The context to use for validation, this is passed to functional validators.
|
||||
self_instance: An instance of a model to set attributes on from validation, this is used when running
|
||||
validation from the `__init__` method of a model.
|
||||
by_alias: Whether to use the field's alias to match the input data to an attribute.
|
||||
by_name: Whether to use the field's name to match the input data to an attribute.
|
||||
"""
|
||||
|
||||
|
||||
StringInput: TypeAlias = 'dict[str, StringInput]'
|
||||
|
||||
|
||||
class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
|
||||
"""Event handler for `SchemaValidator.validate_strings`."""
|
||||
|
||||
def on_enter(
|
||||
self,
|
||||
input: StringInput,
|
||||
*,
|
||||
strict: bool | None = None,
|
||||
extra: ExtraValues | None = None,
|
||||
context: Any | None = None,
|
||||
by_alias: bool | None = None,
|
||||
by_name: bool | None = None,
|
||||
) -> None:
|
||||
"""Callback to be notified of validation start, and create an instance of the event handler.
|
||||
|
||||
Args:
|
||||
input: The string data to be validated.
|
||||
strict: Whether to validate the object in strict mode.
|
||||
extra: Whether to ignore, allow, or forbid extra data during model validation.
|
||||
context: The context to use for validation, this is passed to functional validators.
|
||||
by_alias: Whether to use the field's alias to match the input data to an attribute.
|
||||
by_name: Whether to use the field's name to match the input data to an attribute.
|
||||
"""
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata as importlib_metadata
|
||||
import os
|
||||
import warnings
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import PydanticPluginProtocol
|
||||
|
||||
|
||||
PYDANTIC_ENTRY_POINT_GROUP: Final[str] = 'pydantic'
|
||||
|
||||
# cache of plugins
|
||||
_plugins: dict[str, PydanticPluginProtocol] | None = None
|
||||
# return no plugins while loading plugins to avoid recursion and errors while importing plugins
|
||||
# this means that if plugins use pydantic
|
||||
_loading_plugins: bool = False
|
||||
|
||||
|
||||
def get_plugins() -> Iterable[PydanticPluginProtocol]:
|
||||
"""Load plugins for Pydantic.
|
||||
|
||||
Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402
|
||||
"""
|
||||
disabled_plugins = os.getenv('PYDANTIC_DISABLE_PLUGINS')
|
||||
global _plugins, _loading_plugins
|
||||
if _loading_plugins:
|
||||
# this happens when plugins themselves use pydantic, we return no plugins
|
||||
return ()
|
||||
elif disabled_plugins in ('__all__', '1', 'true'):
|
||||
return ()
|
||||
elif _plugins is None:
|
||||
_plugins = {}
|
||||
# set _loading_plugins so any plugins that use pydantic don't themselves use plugins
|
||||
_loading_plugins = True
|
||||
try:
|
||||
for dist in importlib_metadata.distributions():
|
||||
for entry_point in dist.entry_points:
|
||||
if entry_point.group != PYDANTIC_ENTRY_POINT_GROUP:
|
||||
continue
|
||||
if entry_point.value in _plugins:
|
||||
continue
|
||||
if disabled_plugins is not None and entry_point.name in disabled_plugins.split(','):
|
||||
continue
|
||||
try:
|
||||
_plugins[entry_point.value] = entry_point.load()
|
||||
except (ImportError, AttributeError) as e:
|
||||
warnings.warn(
|
||||
f'{e.__class__.__name__} while loading the `{entry_point.name}` Pydantic plugin, '
|
||||
f'this plugin will not be installed.\n\n{e!r}',
|
||||
stacklevel=2,
|
||||
)
|
||||
finally:
|
||||
_loading_plugins = False
|
||||
|
||||
return _plugins.values()
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Pluggable schema validator for pydantic."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
||||
|
||||
from pydantic_core import CoreConfig, CoreSchema, SchemaValidator, ValidationError
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import BaseValidateHandlerProtocol, PydanticPluginProtocol, SchemaKind, SchemaTypePath
|
||||
|
||||
|
||||
P = ParamSpec('P')
|
||||
R = TypeVar('R')
|
||||
Event = Literal['on_validate_python', 'on_validate_json', 'on_validate_strings']
|
||||
events: list[Event] = list(Event.__args__) # type: ignore
|
||||
|
||||
|
||||
def create_schema_validator(
|
||||
schema: CoreSchema,
|
||||
schema_type: Any,
|
||||
schema_type_module: str,
|
||||
schema_type_name: str,
|
||||
schema_kind: SchemaKind,
|
||||
config: CoreConfig | None = None,
|
||||
plugin_settings: dict[str, Any] | None = None,
|
||||
_use_prebuilt: bool = True,
|
||||
) -> SchemaValidator | PluggableSchemaValidator:
|
||||
"""Create a `SchemaValidator` or `PluggableSchemaValidator` if plugins are installed.
|
||||
|
||||
Returns:
|
||||
If plugins are installed then return `PluggableSchemaValidator`, otherwise return `SchemaValidator`.
|
||||
"""
|
||||
from . import SchemaTypePath
|
||||
from ._loader import get_plugins
|
||||
|
||||
plugins = get_plugins()
|
||||
if plugins:
|
||||
return PluggableSchemaValidator(
|
||||
schema,
|
||||
schema_type,
|
||||
SchemaTypePath(schema_type_module, schema_type_name),
|
||||
schema_kind,
|
||||
config,
|
||||
plugins,
|
||||
plugin_settings or {},
|
||||
_use_prebuilt=_use_prebuilt,
|
||||
)
|
||||
else:
|
||||
return SchemaValidator(schema, config, _use_prebuilt=_use_prebuilt)
|
||||
|
||||
|
||||
class PluggableSchemaValidator:
|
||||
"""Pluggable schema validator."""
|
||||
|
||||
__slots__ = '_schema_validator', 'validate_json', 'validate_python', 'validate_strings'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schema: CoreSchema,
|
||||
schema_type: Any,
|
||||
schema_type_path: SchemaTypePath,
|
||||
schema_kind: SchemaKind,
|
||||
config: CoreConfig | None,
|
||||
plugins: Iterable[PydanticPluginProtocol],
|
||||
plugin_settings: dict[str, Any],
|
||||
_use_prebuilt: bool = True,
|
||||
) -> None:
|
||||
self._schema_validator = SchemaValidator(schema, config, _use_prebuilt=_use_prebuilt)
|
||||
|
||||
python_event_handlers: list[BaseValidateHandlerProtocol] = []
|
||||
json_event_handlers: list[BaseValidateHandlerProtocol] = []
|
||||
strings_event_handlers: list[BaseValidateHandlerProtocol] = []
|
||||
for plugin in plugins:
|
||||
try:
|
||||
p, j, s = plugin.new_schema_validator(
|
||||
schema, schema_type, schema_type_path, schema_kind, config, plugin_settings
|
||||
)
|
||||
except TypeError as e: # pragma: no cover
|
||||
raise TypeError(f'Error using plugin `{plugin.__module__}:{plugin.__class__.__name__}`: {e}') from e
|
||||
if p is not None:
|
||||
python_event_handlers.append(p)
|
||||
if j is not None:
|
||||
json_event_handlers.append(j)
|
||||
if s is not None:
|
||||
strings_event_handlers.append(s)
|
||||
|
||||
self.validate_python = build_wrapper(self._schema_validator.validate_python, python_event_handlers)
|
||||
self.validate_json = build_wrapper(self._schema_validator.validate_json, json_event_handlers)
|
||||
self.validate_strings = build_wrapper(self._schema_validator.validate_strings, strings_event_handlers)
|
||||
|
||||
# Used to allow reuse of validators in pydantic-core (see pydantic-core/src/validators/prebuilt.rs):
|
||||
@property
|
||||
def __pydantic_schema_validator__(self) -> SchemaValidator:
|
||||
"""The underlying pydantic-core `SchemaValidator`.
|
||||
|
||||
This is a private attribute, not meant to be used outside Pydantic.
|
||||
"""
|
||||
return self._schema_validator
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return getattr(self._schema_validator, name)
|
||||
|
||||
|
||||
def build_wrapper(func: Callable[P, R], event_handlers: list[BaseValidateHandlerProtocol]) -> Callable[P, R]:
|
||||
if not event_handlers:
|
||||
return func
|
||||
else:
|
||||
on_enters = tuple(h.on_enter for h in event_handlers if filter_handlers(h, 'on_enter'))
|
||||
on_successes = tuple(h.on_success for h in event_handlers if filter_handlers(h, 'on_success'))
|
||||
on_errors = tuple(h.on_error for h in event_handlers if filter_handlers(h, 'on_error'))
|
||||
on_exceptions = tuple(h.on_exception for h in event_handlers if filter_handlers(h, 'on_exception'))
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
for on_enter_handler in on_enters:
|
||||
on_enter_handler(*args, **kwargs)
|
||||
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
except ValidationError as error:
|
||||
for on_error_handler in on_errors:
|
||||
on_error_handler(error)
|
||||
raise
|
||||
except Exception as exception:
|
||||
for on_exception_handler in on_exceptions:
|
||||
on_exception_handler(exception)
|
||||
raise
|
||||
else:
|
||||
for on_success_handler in on_successes:
|
||||
on_success_handler(result)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def filter_handlers(handler_cls: BaseValidateHandlerProtocol, method_name: str) -> bool:
|
||||
"""Filter out handler methods which are not implemented by the plugin directly - e.g. those that are missing
|
||||
or are inherited from the protocol.
|
||||
"""
|
||||
handler = getattr(handler_cls, method_name, None)
|
||||
if handler is None:
|
||||
return False
|
||||
elif handler.__module__ == 'pydantic.plugin':
|
||||
# this is the original handler, from the protocol due to runtime inheritance
|
||||
# we don't want to call it
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
Reference in New Issue
Block a user