Compare commits

..

2 Commits

Author SHA1 Message Date
dfdc31512d 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
2025-09-17 21:56:47 +02:00
ab7ff7da93 Made startup/app extensions static 2025-09-17 20:54:21 +02:00
18 changed files with 135 additions and 68 deletions

View File

@@ -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

View File

@@ -4,8 +4,7 @@ from cpl.dependency import ServiceProviderABC
class ApplicationExtensionABC(ABC):
@abstractmethod
def __init__(self): ...
@staticmethod
@abstractmethod
def run(self, services: ServiceProviderABC): ...
def run(services: ServiceProviderABC): ...

View File

@@ -6,15 +6,14 @@ from cpl.dependency.service_collection import ServiceCollection
class StartupABC(ABC):
r"""ABC for the startup class"""
@staticmethod
@abstractmethod
def __init__(self): ...
@abstractmethod
def configure_configuration(self):
def configure_configuration():
r"""Creates configuration of application"""
@staticmethod
@abstractmethod
def configure_services(self, service: ServiceCollection):
def configure_services(service: ServiceCollection):
r"""Creates service provider
Parameter:

View File

@@ -6,15 +6,14 @@ from cpl.dependency import ServiceCollection
class StartupExtensionABC(ABC):
r"""ABC for startup extension classes"""
@staticmethod
@abstractmethod
def __init__(self): ...
@abstractmethod
def configure_configuration(self):
def configure_configuration():
r"""Creates configuration of application"""
@staticmethod
@abstractmethod
def configure_services(self, services: ServiceCollection):
def configure_services(services: ServiceCollection):
r"""Creates service provider
Parameter:
services: :class:`cpl.dependency.service_collection`

View File

@@ -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":
self._startup = startup()
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":
@@ -50,8 +50,7 @@ class ApplicationBuilder:
return self
def build(self) -> ApplicationABC:
for ex in self._startup_extensions:
extension = ex()
for extension in self._startup_extensions:
Host.run(extension.configure_configuration)
Host.run(extension.configure_services, self._services)
@@ -59,8 +58,7 @@ class ApplicationBuilder:
Host.run(self._startup.configure_configuration)
Host.run(self._startup.configure_services, self._services)
for ex in self._app_extensions:
extension = ex()
for extension in self._app_extensions:
Host.run(extension.run, self.service_provider)
return self._app(self.service_provider)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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!")

View File

@@ -1,30 +1,26 @@
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
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
async def configure_configuration(self):
@staticmethod
async def configure_configuration():
Configuration.add_json_file(f"appsettings.json")
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
async def configure_services(self, services: ServiceCollection):
@staticmethod
async def configure_services(services: ServiceCollection):
services.add_module(mysql)
services.add_module(auth)
services.add_module(permission)
@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -1,22 +1,22 @@
from cpl import mail
from cpl.application.abc import StartupABC
from cpl.core.configuration import Configuration
from cpl.dependency import ServiceCollection, ServiceProviderABC
from cpl.core.environment import Environment
from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceCollection
from test_service import TestService
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
def configure_configuration(selft):
@staticmethod
def configure_configuration():
Configuration.add_json_file(f"appsettings.json")
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
def configure_services(self, services: ServiceCollection):
@staticmethod
def configure_services(services: ServiceCollection):
services.add_logging()
services.add_module(mail)
services.add_transient(IPAddressPipe)

View File

@@ -4,8 +4,7 @@ from cpl.dependency import ServiceProviderABC
class TestExtension(ApplicationExtensionABC):
def __init__(self):
ApplicationExtensionABC.__init__(self)
def run(self, services: ServiceProviderABC):
@staticmethod
def run(services: ServiceProviderABC):
Console.write_line("Hello World from App Extension")

View File

@@ -1,16 +1,14 @@
from cpl.application.abc import StartupExtensionABC
from cpl.core.configuration import Configuration
from cpl.core.console import Console
from cpl.dependency import ServiceCollection
from cpl.core.environment import Environment
class TestStartupExtension(StartupExtensionABC):
def __init__(self):
StartupExtensionABC.__init__(self)
def configure_configuration(self):
@staticmethod
def configure_configuration():
Console.write_line("config")
def configure_services(self, services: ServiceCollection):
@staticmethod
def configure_services(services: ServiceCollection):
Console.write_line("services")

View File

@@ -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()