App with extension functions
All checks were successful
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 17s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 14s
Build on push / translation (push) Successful in 14s
Build on push / database (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / application (push) Successful in 22s
Build on push / auth (push) Successful in 14s

This commit is contained in:
2025-09-17 21:56:47 +02:00
parent ab7ff7da93
commit dfdc31512d
12 changed files with 107 additions and 32 deletions

View File

@@ -1,10 +1,15 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Callable, Self
from cpl.application.host import Host from cpl.application.host import Host
from cpl.core.console.console import Console from cpl.core.console.console import Console
from cpl.dependency.service_provider_abc import ServiceProviderABC 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): class ApplicationABC(ABC):
r"""ABC for the Application class r"""ABC for the Application class
@@ -17,6 +22,43 @@ class ApplicationABC(ABC):
def __init__(self, services: ServiceProviderABC): def __init__(self, services: ServiceProviderABC):
self._services = services 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): def run(self):
r"""Entry point r"""Entry point

View File

@@ -1,5 +1,5 @@
import asyncio 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_abc import ApplicationABC
from cpl.application.abc.application_extension_abc import ApplicationExtensionABC from cpl.application.abc.application_extension_abc import ApplicationExtensionABC
@@ -34,11 +34,11 @@ class ApplicationBuilder:
def service_provider(self): def service_provider(self):
return self._services.build() return self._services.build()
def use_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder": def with_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder":
self._startup = startup self._startup = startup
return self return self
def use_extension( def with_extension(
self, self,
extension: Type[ApplicationExtensionABC | StartupExtensionABC], extension: Type[ApplicationExtensionABC | StartupExtensionABC],
) -> "ApplicationBuilder": ) -> "ApplicationBuilder":

View File

@@ -1,13 +1,24 @@
from cpl.auth import permission as _permission from enum import Enum
from cpl.auth.keycloak.keycloak_admin import KeycloakAdmin from typing import Type
from cpl.auth.keycloak.keycloak_client import KeycloakClient
from cpl.dependency import ServiceCollection as _ServiceCollection
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 .auth_logger import AuthLogger
from .keycloak_settings import KeycloakSettings from .keycloak_settings import KeycloakSettings
from .permission_seeder import PermissionSeeder 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): def _add_daos(collection: _ServiceCollection):
from .schema._administration.auth_user_dao import AuthUserDao from .schema._administration.auth_user_dao import AuthUserDao
from .schema._administration.api_key_dao import ApiKeyDao 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 from cpl.database.model.server_type import ServerType, ServerTypes
try: try:
collection.add_singleton(KeycloakClient) collection.add_singleton(_KeycloakClient)
collection.add_singleton(KeycloakAdmin) collection.add_singleton(_KeycloakAdmin)
_add_daos(collection) _add_daos(collection)
@@ -68,3 +79,4 @@ def add_permission(collection: _ServiceCollection):
_ServiceCollection.with_module(add_auth, __name__) _ServiceCollection.with_module(add_auth, __name__)
_ServiceCollection.with_module(add_permission, _permission.__name__) _ServiceCollection.with_module(add_permission, _permission.__name__)
_ApplicationABC.extend(_ApplicationABC.with_permissions, _with_permissions)

View File

@@ -5,7 +5,7 @@ from typing import Optional
from async_property import async_property from async_property import async_property
from keycloak import KeycloakGetError 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.auth_logger import AuthLogger
from cpl.auth.permission.permissions import Permissions from cpl.auth.permission.permissions import Permissions
from cpl.core.typing import SerialId from cpl.core.typing import SerialId

View File

@@ -1,11 +1,31 @@
from typing import Type from typing import Type
from cpl.application.abc import ApplicationABC as _ApplicationABC
from cpl.dependency import ServiceCollection as _ServiceCollection from cpl.dependency import ServiceCollection as _ServiceCollection
from . import mysql as _mysql from . import mysql as _mysql
from . import postgres as _postgres from . import postgres as _postgres
from .table_manager import TableManager 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): def _add(collection: _ServiceCollection, db_context: Type, default_port: int, server_type: str):
from cpl.core.console import Console from cpl.core.console import Console
from cpl.core.configuration import Configuration 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_mysql, _mysql.__name__)
_ServiceCollection.with_module(add_postgres, _postgres.__name__) _ServiceCollection.with_module(add_postgres, _postgres.__name__)
_ApplicationABC.extend(_ApplicationABC.with_migrations, _with_migrations)
_ApplicationABC.extend(_ApplicationABC.with_seeders, _with_seeders)

View File

@@ -1,5 +1,5 @@
from cpl.application.abc.application_abc import ApplicationABC 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.console import Console
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.log import LoggerABC from cpl.core.log import LoggerABC

View File

@@ -1,11 +1,16 @@
from application import Application from application import Application
from cpl.application import ApplicationBuilder from cpl.application import ApplicationBuilder
from custom_permissions import CustomPermissions
from startup import Startup from startup import Startup
def main(): def main():
builder = ApplicationBuilder(Application).use_startup(Startup) builder = ApplicationBuilder(Application).with_startup(Startup)
app = builder.build() app = builder.build()
app.with_permissions(CustomPermissions)
app.with_migrations("./scripts")
app.with_seeders()
app.run() app.run()

View File

@@ -1,12 +1,20 @@
from application import Application from application import Application
from cpl.application import ApplicationBuilder from cpl.application import ApplicationBuilder
from cpl.auth.permission.permissions_registry import PermissionsRegistry
from cpl.core.console import Console from cpl.core.console import Console
from custom_permissions import CustomPermissions
from startup import Startup from startup import Startup
def main(): def main():
builder = ApplicationBuilder(Application).use_startup(Startup) builder = ApplicationBuilder(Application).with_startup(Startup)
app = builder.build() 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() app.run()
Console.write_line("Hello from main_simplified.py!") Console.write_line("Hello from main_simplified.py!")

View File

@@ -1,16 +1,12 @@
from cpl import auth from cpl import auth
from cpl.application.abc.startup_abc import StartupABC from cpl.application.abc.startup_abc import StartupABC
from cpl.auth import permission from cpl.auth import permission
from cpl.auth.permission.permissions_registry import PermissionsRegistry
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.log import Logger, LoggerABC from cpl.core.log import Logger, LoggerABC
from cpl.database import mysql from cpl.database import mysql
from cpl.database.abc.data_access_object_abc import DataAccessObjectABC 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 cpl.dependency import ServiceCollection
from custom_permissions import CustomPermissions
from model.city_dao import CityDao from model.city_dao import CityDao
from model.user_dao import UserDao from model.user_dao import UserDao
@@ -33,13 +29,3 @@ class Startup(StartupABC):
services.add_transient(DataAccessObjectABC, CityDao) services.add_transient(DataAccessObjectABC, CityDao)
services.add_singleton(LoggerABC, Logger) 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()

View File

@@ -6,7 +6,7 @@ from di.startup import Startup
def main(): def main():
app_builder = ApplicationBuilder(Application) app_builder = ApplicationBuilder(Application)
app_builder.use_startup(Startup) app_builder.with_startup(Startup)
app_builder.build().run() app_builder.build().run()

View File

@@ -7,9 +7,9 @@ from test_startup_extension import TestStartupExtension
def main(): def main():
app_builder = ApplicationBuilder(Application) app_builder = ApplicationBuilder(Application)
app_builder.use_startup(Startup) app_builder.with_startup(Startup)
app_builder.use_extension(TestStartupExtension) app_builder.with_extension(TestStartupExtension)
app_builder.use_extension(TestExtension) app_builder.with_extension(TestExtension)
app_builder.build().run() app_builder.build().run()

View File

@@ -6,7 +6,7 @@ from translation.startup import Startup
def main(): def main():
app_builder = ApplicationBuilder(Application) app_builder = ApplicationBuilder(Application)
app_builder.use_startup(Startup) app_builder.with_startup(Startup)
app_builder.build().run() app_builder.build().run()