From dfdc31512d4f93833a2505a39daec7466936d115 Mon Sep 17 00:00:00 2001 From: edraft Date: Wed, 17 Sep 2025 21:56:47 +0200 Subject: [PATCH] App with extension functions --- .../cpl/application/abc/application_abc.py | 42 +++++++++++++++++++ .../cpl/application/application_builder.py | 6 +-- src/cpl-auth/cpl/auth/__init__.py | 24 ++++++++--- .../auth/schema/_administration/auth_user.py | 2 +- src/cpl-database/cpl/database/__init__.py | 22 ++++++++++ tests/custom/database/src/application.py | 2 +- tests/custom/database/src/main.py | 7 +++- tests/custom/database/src/main_simplified.py | 10 ++++- tests/custom/database/src/startup.py | 14 ------- tests/custom/di/src/di/main.py | 2 +- tests/custom/general/src/general/main.py | 6 +-- .../translation/src/translation/main.py | 2 +- 12 files changed, 107 insertions(+), 32 deletions(-) diff --git a/src/cpl-application/cpl/application/abc/application_abc.py b/src/cpl-application/cpl/application/abc/application_abc.py index 6905cdbc..4199eb1d 100644 --- a/src/cpl-application/cpl/application/abc/application_abc.py +++ b/src/cpl-application/cpl/application/abc/application_abc.py @@ -1,10 +1,15 @@ from abc import ABC, abstractmethod +from typing import Callable, Self from cpl.application.host import Host from cpl.core.console.console import Console from cpl.dependency.service_provider_abc import ServiceProviderABC +def __not_implemented__(package: str, func: Callable): + raise NotImplementedError(f"Package {package} is required to use {func.__name__} method") + + class ApplicationABC(ABC): r"""ABC for the Application class @@ -17,6 +22,43 @@ class ApplicationABC(ABC): def __init__(self, services: ServiceProviderABC): self._services = services + @classmethod + def extend(cls, name: str | Callable, func: Callable[[Self], Self]): + r"""Extend the Application with a custom method + + Parameters: + name: :class:`str` + Name of the method + func: :class:`Callable[[Self], Self]` + Function that takes the Application as a parameter and returns it + """ + if callable(name): + name = name.__name__ + + setattr(cls, name, func) + return cls + + def with_permissions(self, *args, **kwargs): + __not_implemented__("cpl-auth", self.with_permissions) + + def with_migrations(self, *args, **kwargs): + __not_implemented__("cpl-database", self.with_migrations) + + def with_seeders(self, *args, **kwargs): + __not_implemented__("cpl-database", self.with_seeders) + + def with_extension(self, func: Callable[[Self, ...], None], *args, **kwargs): + r"""Extend the Application with a custom method + + Parameters: + func: :class:`Callable[[Self], Self]` + Function that takes the Application as a parameter and returns it + """ + assert func is not None, "func must not be None" + assert callable(func), "func must be callable" + + func(self, *args, **kwargs) + def run(self): r"""Entry point diff --git a/src/cpl-application/cpl/application/application_builder.py b/src/cpl-application/cpl/application/application_builder.py index 37ff65fa..7e329c9b 100644 --- a/src/cpl-application/cpl/application/application_builder.py +++ b/src/cpl-application/cpl/application/application_builder.py @@ -1,5 +1,5 @@ import asyncio -from typing import Type, Optional, Callable +from typing import Type, Optional from cpl.application.abc.application_abc import ApplicationABC from cpl.application.abc.application_extension_abc import ApplicationExtensionABC @@ -34,11 +34,11 @@ class ApplicationBuilder: def service_provider(self): return self._services.build() - def use_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder": + def with_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder": self._startup = startup return self - def use_extension( + def with_extension( self, extension: Type[ApplicationExtensionABC | StartupExtensionABC], ) -> "ApplicationBuilder": diff --git a/src/cpl-auth/cpl/auth/__init__.py b/src/cpl-auth/cpl/auth/__init__.py index 8eb37acb..e7f292a5 100644 --- a/src/cpl-auth/cpl/auth/__init__.py +++ b/src/cpl-auth/cpl/auth/__init__.py @@ -1,13 +1,24 @@ -from cpl.auth import permission as _permission -from cpl.auth.keycloak.keycloak_admin import KeycloakAdmin -from cpl.auth.keycloak.keycloak_client import KeycloakClient -from cpl.dependency import ServiceCollection as _ServiceCollection +from enum import Enum +from typing import Type +from cpl.application.abc import ApplicationABC as _ApplicationABC +from cpl.auth import permission as _permission +from cpl.auth.keycloak.keycloak_admin import KeycloakAdmin as _KeycloakAdmin +from cpl.auth.keycloak.keycloak_client import KeycloakClient as _KeycloakClient +from cpl.dependency.service_collection import ServiceCollection as _ServiceCollection from .auth_logger import AuthLogger from .keycloak_settings import KeycloakSettings from .permission_seeder import PermissionSeeder +def _with_permissions(self: _ApplicationABC, *permissions: Type[Enum]) -> _ApplicationABC: + from cpl.auth.permission.permissions_registry import PermissionsRegistry + + for perm in permissions: + PermissionsRegistry.with_enum(perm) + return self + + def _add_daos(collection: _ServiceCollection): from .schema._administration.auth_user_dao import AuthUserDao from .schema._administration.api_key_dao import ApiKeyDao @@ -34,8 +45,8 @@ def add_auth(collection: _ServiceCollection): from cpl.database.model.server_type import ServerType, ServerTypes try: - collection.add_singleton(KeycloakClient) - collection.add_singleton(KeycloakAdmin) + collection.add_singleton(_KeycloakClient) + collection.add_singleton(_KeycloakAdmin) _add_daos(collection) @@ -68,3 +79,4 @@ def add_permission(collection: _ServiceCollection): _ServiceCollection.with_module(add_auth, __name__) _ServiceCollection.with_module(add_permission, _permission.__name__) +_ApplicationABC.extend(_ApplicationABC.with_permissions, _with_permissions) diff --git a/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py b/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py index 0ff2a97c..da8ec803 100644 --- a/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py +++ b/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py @@ -5,7 +5,7 @@ from typing import Optional from async_property import async_property from keycloak import KeycloakGetError -from cpl.auth import KeycloakAdmin +from cpl.auth.keycloak import KeycloakAdmin from cpl.auth.auth_logger import AuthLogger from cpl.auth.permission.permissions import Permissions from cpl.core.typing import SerialId diff --git a/src/cpl-database/cpl/database/__init__.py b/src/cpl-database/cpl/database/__init__.py index 5fbaede7..6dc71798 100644 --- a/src/cpl-database/cpl/database/__init__.py +++ b/src/cpl-database/cpl/database/__init__.py @@ -1,11 +1,31 @@ from typing import Type +from cpl.application.abc import ApplicationABC as _ApplicationABC from cpl.dependency import ServiceCollection as _ServiceCollection from . import mysql as _mysql from . import postgres as _postgres from .table_manager import TableManager +def _with_migrations(self: _ApplicationABC, *paths: list[str]) -> _ApplicationABC: + from cpl.application.host import Host + + from cpl.database.service.migration_service import MigrationService + migration_service = self._services.get_service(MigrationService) + migration_service.with_directory("./scripts") + Host.run(migration_service.migrate) + + return self + +def _with_seeders(self: _ApplicationABC) -> _ApplicationABC: + from cpl.database.service.seeder_service import SeederService + from cpl.application.host import Host + + seeder_service: SeederService = self._services.get_service(SeederService) + Host.run(seeder_service.seed) + return self + + def _add(collection: _ServiceCollection, db_context: Type, default_port: int, server_type: str): from cpl.core.console import Console from cpl.core.configuration import Configuration @@ -44,3 +64,5 @@ def add_postgres(collection: _ServiceCollection): _ServiceCollection.with_module(add_mysql, _mysql.__name__) _ServiceCollection.with_module(add_postgres, _postgres.__name__) +_ApplicationABC.extend(_ApplicationABC.with_migrations, _with_migrations) +_ApplicationABC.extend(_ApplicationABC.with_seeders, _with_seeders) diff --git a/tests/custom/database/src/application.py b/tests/custom/database/src/application.py index 8d6e187a..0a1190a0 100644 --- a/tests/custom/database/src/application.py +++ b/tests/custom/database/src/application.py @@ -1,5 +1,5 @@ from cpl.application.abc.application_abc import ApplicationABC -from cpl.auth import KeycloakAdmin +from cpl.auth.keycloak import KeycloakAdmin from cpl.core.console import Console from cpl.core.environment import Environment from cpl.core.log import LoggerABC diff --git a/tests/custom/database/src/main.py b/tests/custom/database/src/main.py index 862e88aa..78e2c659 100644 --- a/tests/custom/database/src/main.py +++ b/tests/custom/database/src/main.py @@ -1,11 +1,16 @@ from application import Application from cpl.application import ApplicationBuilder +from custom_permissions import CustomPermissions from startup import Startup def main(): - builder = ApplicationBuilder(Application).use_startup(Startup) + builder = ApplicationBuilder(Application).with_startup(Startup) app = builder.build() + + app.with_permissions(CustomPermissions) + app.with_migrations("./scripts") + app.with_seeders() app.run() diff --git a/tests/custom/database/src/main_simplified.py b/tests/custom/database/src/main_simplified.py index cc463bf8..3fdadeb1 100644 --- a/tests/custom/database/src/main_simplified.py +++ b/tests/custom/database/src/main_simplified.py @@ -1,12 +1,20 @@ from application import Application from cpl.application import ApplicationBuilder +from cpl.auth.permission.permissions_registry import PermissionsRegistry from cpl.core.console import Console +from custom_permissions import CustomPermissions from startup import Startup def main(): - builder = ApplicationBuilder(Application).use_startup(Startup) + builder = ApplicationBuilder(Application).with_startup(Startup) app = builder.build() + + app.with_permissions(CustomPermissions) + app.with_migrations("./scripts") + app.with_seeders() + + Console.write_line(CustomPermissions.test.value in PermissionsRegistry.get()) app.run() Console.write_line("Hello from main_simplified.py!") diff --git a/tests/custom/database/src/startup.py b/tests/custom/database/src/startup.py index 74f75761..d9eab6c8 100644 --- a/tests/custom/database/src/startup.py +++ b/tests/custom/database/src/startup.py @@ -1,16 +1,12 @@ from cpl import auth from cpl.application.abc.startup_abc import StartupABC from cpl.auth import permission -from cpl.auth.permission.permissions_registry import PermissionsRegistry from cpl.core.configuration import Configuration from cpl.core.environment import Environment from cpl.core.log import Logger, LoggerABC from cpl.database import mysql from cpl.database.abc.data_access_object_abc import DataAccessObjectABC -from cpl.database.service.migration_service import MigrationService -from cpl.database.service.seeder_service import SeederService from cpl.dependency import ServiceCollection -from custom_permissions import CustomPermissions from model.city_dao import CityDao from model.user_dao import UserDao @@ -33,13 +29,3 @@ class Startup(StartupABC): services.add_transient(DataAccessObjectABC, CityDao) services.add_singleton(LoggerABC, Logger) - - PermissionsRegistry.with_enum(CustomPermissions) - - provider = services.build() - migration_service: MigrationService = provider.get_service(MigrationService) - - migration_service.with_directory("./scripts") - await migration_service.migrate() - seeder_service: SeederService = provider.get_service(SeederService) - await seeder_service.seed() diff --git a/tests/custom/di/src/di/main.py b/tests/custom/di/src/di/main.py index 5982b52e..a5ba63d8 100644 --- a/tests/custom/di/src/di/main.py +++ b/tests/custom/di/src/di/main.py @@ -6,7 +6,7 @@ from di.startup import Startup def main(): app_builder = ApplicationBuilder(Application) - app_builder.use_startup(Startup) + app_builder.with_startup(Startup) app_builder.build().run() diff --git a/tests/custom/general/src/general/main.py b/tests/custom/general/src/general/main.py index 7a490f79..2168415b 100644 --- a/tests/custom/general/src/general/main.py +++ b/tests/custom/general/src/general/main.py @@ -7,9 +7,9 @@ from test_startup_extension import TestStartupExtension def main(): app_builder = ApplicationBuilder(Application) - app_builder.use_startup(Startup) - app_builder.use_extension(TestStartupExtension) - app_builder.use_extension(TestExtension) + app_builder.with_startup(Startup) + app_builder.with_extension(TestStartupExtension) + app_builder.with_extension(TestExtension) app_builder.build().run() diff --git a/tests/custom/translation/src/translation/main.py b/tests/custom/translation/src/translation/main.py index 094d8ee0..a2b972be 100644 --- a/tests/custom/translation/src/translation/main.py +++ b/tests/custom/translation/src/translation/main.py @@ -6,7 +6,7 @@ from translation.startup import Startup def main(): app_builder = ApplicationBuilder(Application) - app_builder.use_startup(Startup) + app_builder.with_startup(Startup) app_builder.build().run()