Compare commits
1 Commits
2025.10.04
...
2025.09.24
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bb264b8c1 |
@@ -25,11 +25,7 @@ jobs:
|
||||
git tag
|
||||
DATE=$(date +'%Y.%m.%d')
|
||||
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
|
||||
if [ "$TAG_COUNT" -eq 0 ]; then
|
||||
BUILD_NUMBER=0
|
||||
else
|
||||
BUILD_NUMBER=$(($TAG_COUNT + 1))
|
||||
fi
|
||||
BUILD_NUMBER=$(($TAG_COUNT + 1))
|
||||
|
||||
VERSION_SUFFIX=${{ inputs.version_suffix }}
|
||||
if [ -n "$VERSION_SUFFIX" ] && [ "$VERSION_SUFFIX" = "dev" ]; then
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from cpl.api.api_module import ApiModule
|
||||
from cpl import api
|
||||
from cpl.api.application.web_app import WebApp
|
||||
from cpl.application.application_builder import ApplicationBuilder
|
||||
from cpl.auth import AuthModule
|
||||
from cpl.application import ApplicationBuilder
|
||||
from cpl.auth.permission.permissions import Permissions
|
||||
from cpl.auth.schema import AuthUser, Role
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.environment import Environment
|
||||
from cpl.core.utils.cache import Cache
|
||||
from cpl.database.mysql.mysql_module import MySQLModule
|
||||
from scoped_service import ScopedService
|
||||
from service import PingService
|
||||
|
||||
@@ -25,8 +23,7 @@ def main():
|
||||
# builder.services.add_logging()
|
||||
builder.services.add_structured_logging()
|
||||
builder.services.add_transient(PingService)
|
||||
builder.services.add_module(MySQLModule)
|
||||
builder.services.add_module(ApiModule)
|
||||
builder.services.add_module(api)
|
||||
|
||||
builder.services.add_scoped(ScopedService)
|
||||
|
||||
@@ -35,17 +32,12 @@ def main():
|
||||
|
||||
app = builder.build()
|
||||
app.with_logging()
|
||||
app.with_database()
|
||||
|
||||
app.with_authentication()
|
||||
app.with_authorization()
|
||||
|
||||
app.with_route(
|
||||
path="/route1",
|
||||
fn=lambda r: JSONResponse("route1"),
|
||||
method="GET",
|
||||
authentication=True,
|
||||
permissions=[Permissions.administrator],
|
||||
)
|
||||
app.with_route(path="/route1", fn=lambda r: JSONResponse("route1"), method="GET", authentication=True, permissions=[Permissions.administrator])
|
||||
app.with_routes_directory("routes")
|
||||
|
||||
provider = builder.service_provider
|
||||
40
example/custom/database/cpl.json
Normal file
40
example/custom/database/cpl.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"Project": {
|
||||
"Name": "database",
|
||||
"Version": {
|
||||
"Major": "0",
|
||||
"Minor": "0",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"sh_cpl==2021.4.2.dev1"
|
||||
],
|
||||
"PythonVersion": ">=3.9.2",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"Build": {
|
||||
"ProjectType": "console",
|
||||
"SourcePath": "src",
|
||||
"OutputPath": "dist",
|
||||
"Main": "main",
|
||||
"EntryPoint": "database",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ from cpl.core.console import Console
|
||||
from cpl.core.environment import Environment
|
||||
from cpl.core.log import LoggerABC
|
||||
from cpl.dependency import ServiceProvider
|
||||
from cpl.dependency.typing import Modules
|
||||
from model.city import City
|
||||
from model.city_dao import CityDao
|
||||
from model.user import User
|
||||
@@ -12,8 +11,8 @@ from model.user_dao import UserDao
|
||||
|
||||
|
||||
class Application(ApplicationABC):
|
||||
def __init__(self, services: ServiceProvider, modules: Modules):
|
||||
ApplicationABC.__init__(self, services, modules)
|
||||
def __init__(self, services: ServiceProvider):
|
||||
ApplicationABC.__init__(self, services)
|
||||
|
||||
self._logger = services.get_service(LoggerABC)
|
||||
|
||||
@@ -3,7 +3,6 @@ from cpl.application import ApplicationBuilder
|
||||
from cpl.auth.permission.permissions_registry import PermissionsRegistry
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log import LogLevel
|
||||
from cpl.database import DatabaseModule
|
||||
from custom_permissions import CustomPermissions
|
||||
from startup import Startup
|
||||
|
||||
@@ -11,12 +10,13 @@ from startup import Startup
|
||||
def main():
|
||||
builder = ApplicationBuilder(Application).with_startup(Startup)
|
||||
builder.services.add_logging()
|
||||
|
||||
app = builder.build()
|
||||
|
||||
app.with_logging(LogLevel.trace)
|
||||
app.with_permissions(CustomPermissions)
|
||||
app.with_migrations("./scripts")
|
||||
# app.with_seeders()
|
||||
app.with_seeders()
|
||||
|
||||
Console.write_line(CustomPermissions.test.value in PermissionsRegistry.get())
|
||||
app.run()
|
||||
@@ -1,14 +1,11 @@
|
||||
from cpl import auth
|
||||
from cpl.application.abc.startup_abc import StartupABC
|
||||
from cpl.auth import permission
|
||||
from cpl.auth.auth_module import AuthModule
|
||||
from cpl.auth.permission.permission_module import PermissionsModule
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.core.environment import Environment
|
||||
from cpl.core.log import Logger, LoggerABC
|
||||
from cpl.database import mysql, DatabaseModule
|
||||
from cpl.database import mysql
|
||||
from cpl.database.abc.data_access_object_abc import DataAccessObjectABC
|
||||
from cpl.database.mysql.mysql_module import MySQLModule
|
||||
from cpl.dependency import ServiceCollection
|
||||
from model.city_dao import CityDao
|
||||
from model.user_dao import UserDao
|
||||
@@ -24,10 +21,9 @@ class Startup(StartupABC):
|
||||
|
||||
@staticmethod
|
||||
async def configure_services(services: ServiceCollection):
|
||||
services.add_module(MySQLModule)
|
||||
services.add_module(DatabaseModule)
|
||||
services.add_module(AuthModule)
|
||||
services.add_module(PermissionsModule)
|
||||
services.add_module(mysql)
|
||||
services.add_module(auth)
|
||||
services.add_module(permission)
|
||||
|
||||
services.add_transient(DataAccessObjectABC, UserDao)
|
||||
services.add_transient(DataAccessObjectABC, CityDao)
|
||||
9
example/custom/di/cpl-workspace.json
Normal file
9
example/custom/di/cpl-workspace.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Workspace": {
|
||||
"DefaultProject": "di",
|
||||
"Projects": {
|
||||
"di": "src/di/di.json"
|
||||
},
|
||||
"Scripts": {}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
from cpl.application.abc import ApplicationABC
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.dependency import ServiceProvider
|
||||
from test_abc import TestABC
|
||||
from test_service import TestService
|
||||
from di_tester_service import DITesterService
|
||||
from tester import Tester
|
||||
from di.static_test import StaticTest
|
||||
from di.test_abc import TestABC
|
||||
from di.test_service import TestService
|
||||
from di.di_tester_service import DITesterService
|
||||
from di.tester import Tester
|
||||
|
||||
|
||||
class Application(ApplicationABC):
|
||||
@@ -38,8 +39,7 @@ class Application(ApplicationABC):
|
||||
|
||||
Console.write_line("Global")
|
||||
self._part_of_scoped()
|
||||
#from static_test import StaticTest
|
||||
#StaticTest.test()
|
||||
StaticTest.test()
|
||||
|
||||
self._services.get_service(Tester)
|
||||
Console.write_line(self._services.get_services(TestABC))
|
||||
@@ -1,5 +1,5 @@
|
||||
from cpl.core.console.console import Console
|
||||
from test_service import TestService
|
||||
from di.test_service import TestService
|
||||
|
||||
|
||||
class DITesterService:
|
||||
@@ -1,7 +1,7 @@
|
||||
from cpl.application import ApplicationBuilder
|
||||
|
||||
from application import Application
|
||||
from startup import Startup
|
||||
from di.application import Application
|
||||
from di.startup import Startup
|
||||
|
||||
|
||||
def main():
|
||||
@@ -1,11 +1,11 @@
|
||||
from cpl.application.abc import StartupABC
|
||||
from cpl.dependency import ServiceProvider, ServiceCollection
|
||||
from di_tester_service import DITesterService
|
||||
from test1_service import Test1Service
|
||||
from test2_service import Test2Service
|
||||
from test_abc import TestABC
|
||||
from test_service import TestService
|
||||
from tester import Tester
|
||||
from di.di_tester_service import DITesterService
|
||||
from di.test1_service import Test1Service
|
||||
from di.test2_service import Test2Service
|
||||
from di.test_abc import TestABC
|
||||
from di.test_service import TestService
|
||||
from di.tester import Tester
|
||||
|
||||
|
||||
class Startup(StartupABC):
|
||||
@@ -1,6 +1,6 @@
|
||||
from cpl.dependency import ServiceProvider, ServiceProvider
|
||||
from cpl.dependency.inject import inject
|
||||
from test_service import TestService
|
||||
from di.test_service import TestService
|
||||
|
||||
|
||||
class StaticTest:
|
||||
@@ -1,7 +1,7 @@
|
||||
import string
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.core.utils.string import String
|
||||
from test_abc import TestABC
|
||||
from di.test_abc import TestABC
|
||||
|
||||
|
||||
class Test1Service(TestABC):
|
||||
@@ -1,7 +1,7 @@
|
||||
import string
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.core.utils.string import String
|
||||
from test_abc import TestABC
|
||||
from di.test_abc import TestABC
|
||||
|
||||
|
||||
class Test2Service(TestABC):
|
||||
8
example/custom/di/src/di/tester.py
Normal file
8
example/custom/di/src/di/tester.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from cpl.core.console.console import Console
|
||||
from di.test_abc import TestABC
|
||||
|
||||
|
||||
class Tester:
|
||||
def __init__(self, t1: TestABC, t2: TestABC, t3: list[TestABC]):
|
||||
Console.write_line("Tester:")
|
||||
Console.write_line(t1, t2, t3)
|
||||
8
example/custom/general/cpl-workspace.json
Normal file
8
example/custom/general/cpl-workspace.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Workspace": {
|
||||
"DefaultProject": "general",
|
||||
"Projects": {
|
||||
"general": "src/general/general.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from cpl.application.abc import ApplicationABC
|
||||
@@ -8,7 +7,6 @@ from cpl.core.environment import Environment
|
||||
from cpl.core.log import LoggerABC
|
||||
from cpl.core.pipes import IPAddressPipe
|
||||
from cpl.dependency import ServiceProvider
|
||||
from cpl.dependency.typing import Modules
|
||||
from cpl.mail import EMail, EMailClientABC
|
||||
from cpl.query import List
|
||||
from scoped_service import ScopedService
|
||||
@@ -18,8 +16,8 @@ from test_settings import TestSettings
|
||||
|
||||
class Application(ApplicationABC):
|
||||
|
||||
def __init__(self, services: ServiceProvider, modules: Modules):
|
||||
ApplicationABC.__init__(self, services, modules)
|
||||
def __init__(self, services: ServiceProvider):
|
||||
ApplicationABC.__init__(self, services)
|
||||
self._logger = self._services.get_service(LoggerABC)
|
||||
self._mailer = self._services.get_service(EMailClientABC)
|
||||
|
||||
@@ -37,7 +35,7 @@ class Application(ApplicationABC):
|
||||
def _wait(time_ms: int):
|
||||
time.sleep(time_ms)
|
||||
|
||||
async def main(self):
|
||||
def main(self):
|
||||
self._logger.debug(f"Host: {Environment.get_host_name()}")
|
||||
self._logger.debug(f"Environment: {Environment.get_environment()}")
|
||||
Console.write_line(List(range(0, 10)).select(lambda x: f"x={x}").to_list())
|
||||
@@ -77,7 +75,7 @@ class Application(ApplicationABC):
|
||||
# self.test_send_mail()
|
||||
|
||||
x = 0
|
||||
while x < 500:
|
||||
while x < 5:
|
||||
Console.write_line("Running...")
|
||||
x += 1
|
||||
await asyncio.sleep(5)
|
||||
time.sleep(5)
|
||||
51
example/custom/general/src/general.json
Normal file
51
example/custom/general/src/general.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"Project": {
|
||||
"Name": "general",
|
||||
"Version": {
|
||||
"Major": "2021",
|
||||
"Minor": "04",
|
||||
"Micro": "01"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
"Description": "sh-edraft Common Python library",
|
||||
"LongDescription": "sh-edraft Common Python library",
|
||||
"URL": "https://www.sh-edraft.de",
|
||||
"CopyrightDate": "2020 - 2021",
|
||||
"CopyrightName": "sh-edraft.de",
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-core==2022.10.0.post9",
|
||||
"cpl-translation==2022.10.0.post2",
|
||||
"cpl-query==2022.10.0.post2"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2022.10"
|
||||
],
|
||||
"PythonVersion": ">=3.10",
|
||||
"PythonPath": {
|
||||
"linux": "../../venv/bin/python",
|
||||
"win32": ""
|
||||
},
|
||||
"Classifiers": []
|
||||
},
|
||||
"Build": {
|
||||
"ProjectType": "console",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "dist",
|
||||
"Main": "main",
|
||||
"EntryPoint": "",
|
||||
"IncludePackageData": true,
|
||||
"Included": [
|
||||
"*/templates"
|
||||
],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.time.cron import Cron
|
||||
from cpl.dependency.hosted.cronjob import CronjobABC
|
||||
from cpl.dependency.hosted.hosted_service import HostedService
|
||||
|
||||
|
||||
@@ -19,12 +17,4 @@ class Hosted(HostedService):
|
||||
|
||||
async def stop(self):
|
||||
Console.write_line("Hosted Service Stopped")
|
||||
self._stopped = True
|
||||
|
||||
|
||||
class MyCronJob(CronjobABC):
|
||||
def __init__(self):
|
||||
CronjobABC.__init__(self, Cron("*/1 * * * *")) # Every minute
|
||||
|
||||
async def loop(self):
|
||||
Console.write_line(f"[{datetime.now()}] Hello from Cronjob!")
|
||||
self._stopped = True
|
||||
@@ -1,10 +1,10 @@
|
||||
from cpl import mail
|
||||
from cpl.application.abc import StartupABC
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.core.environment import Environment
|
||||
from cpl.core.pipes import IPAddressPipe
|
||||
from cpl.dependency import ServiceCollection
|
||||
from cpl.mail.mail_module import MailModule
|
||||
from hosted_service import Hosted, MyCronJob
|
||||
from example.custom.general.src.hosted_service import Hosted
|
||||
from scoped_service import ScopedService
|
||||
from test_service import TestService
|
||||
|
||||
@@ -20,9 +20,8 @@ class Startup(StartupABC):
|
||||
@staticmethod
|
||||
def configure_services(services: ServiceCollection):
|
||||
services.add_logging()
|
||||
services.add_module(MailModule)
|
||||
services.add_module(mail)
|
||||
services.add_transient(IPAddressPipe)
|
||||
services.add_singleton(TestService)
|
||||
services.add_scoped(ScopedService)
|
||||
services.add_hosted_service(Hosted)
|
||||
services.add_hosted_service(MyCronJob)
|
||||
3
example/custom/general/test/custom.py
Normal file
3
example/custom/general/test/custom.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class Custom:
|
||||
def __init__(self):
|
||||
print("hello")
|
||||
@@ -48,9 +48,9 @@ def t_benchmark(data: list):
|
||||
|
||||
|
||||
def main():
|
||||
N = 1_000_000
|
||||
N = 10_000_000
|
||||
data = list(range(N))
|
||||
t_benchmark(data)
|
||||
#t_benchmark(data)
|
||||
|
||||
Console.write_line()
|
||||
_default()
|
||||
0
example/custom/translation/src/tests/__init__.py
Normal file
0
example/custom/translation/src/tests/__init__.py
Normal file
@@ -1,7 +0,0 @@
|
||||
from cpl.core.console.console import Console
|
||||
from test_abc import TestABC
|
||||
|
||||
|
||||
class Tester:
|
||||
def __init__(self, t1: TestABC, t2: TestABC, t3: TestABC, t: list[TestABC]):
|
||||
Console.write_line("Tester:", t, t1, t2, t3)
|
||||
@@ -1,4 +1,36 @@
|
||||
from cpl.dependency.service_collection import ServiceCollection as _ServiceCollection
|
||||
|
||||
from .error import APIError, AlreadyExists, EndpointNotImplemented, Forbidden, NotFound, Unauthorized
|
||||
from .logger import APILogger
|
||||
from .settings import ApiSettings
|
||||
from .api_module import ApiModule
|
||||
|
||||
|
||||
def add_api(collection: _ServiceCollection):
|
||||
try:
|
||||
from cpl.database import mysql
|
||||
|
||||
collection.add_module(mysql)
|
||||
except ImportError as e:
|
||||
from cpl.core.errors import dependency_error
|
||||
|
||||
dependency_error("cpl-database", e)
|
||||
|
||||
try:
|
||||
from cpl import auth
|
||||
from cpl.auth import permission
|
||||
|
||||
collection.add_module(auth)
|
||||
collection.add_module(permission)
|
||||
except ImportError as e:
|
||||
from cpl.core.errors import dependency_error
|
||||
|
||||
dependency_error("cpl-auth", e)
|
||||
|
||||
from cpl.api.registry.policy import PolicyRegistry
|
||||
from cpl.api.registry.route import RouteRegistry
|
||||
|
||||
collection.add_singleton(PolicyRegistry)
|
||||
collection.add_singleton(RouteRegistry)
|
||||
|
||||
|
||||
_ServiceCollection.with_module(add_api, __name__)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
from cpl.api import ApiSettings
|
||||
from cpl.api.registry.policy import PolicyRegistry
|
||||
from cpl.api.registry.route import RouteRegistry
|
||||
from cpl.auth.auth_module import AuthModule
|
||||
from cpl.auth.permission.permission_module import PermissionsModule
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.dependency import ServiceCollection
|
||||
from cpl.dependency.module.module import Module
|
||||
|
||||
|
||||
class ApiModule(Module):
|
||||
config = [ApiSettings]
|
||||
singleton = [
|
||||
PolicyRegistry,
|
||||
RouteRegistry,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
collection.add_module(DatabaseModule)
|
||||
collection.add_module(AuthModule)
|
||||
collection.add_module(PermissionsModule)
|
||||
@@ -10,7 +10,7 @@ from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.types import ExceptionHandler
|
||||
|
||||
from cpl.api.api_module import ApiModule
|
||||
from cpl import api, auth
|
||||
from cpl.api.error import APIError
|
||||
from cpl.api.logger import APILogger
|
||||
from cpl.api.middleware.authentication import AuthenticationMiddleware
|
||||
@@ -26,19 +26,16 @@ from cpl.api.router import Router
|
||||
from cpl.api.settings import ApiSettings
|
||||
from cpl.api.typing import HTTPMethods, PartialMiddleware, PolicyResolver
|
||||
from cpl.application.abc.application_abc import ApplicationABC
|
||||
from cpl.auth.auth_module import AuthModule
|
||||
from cpl.auth.permission.permission_module import PermissionsModule
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.dependency.inject import inject
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
from cpl.dependency.typing import Modules
|
||||
|
||||
PolicyInput = Union[dict[str, PolicyResolver], Policy]
|
||||
|
||||
|
||||
class WebApp(ApplicationABC):
|
||||
def __init__(self, services: ServiceProvider, modules: Modules):
|
||||
super().__init__(services, modules, [AuthModule, PermissionsModule, ApiModule])
|
||||
def __init__(self, services: ServiceProvider):
|
||||
super().__init__(services, [auth, api])
|
||||
self._app: Starlette | None = None
|
||||
|
||||
self._logger = services.get_service(APILogger)
|
||||
@@ -78,6 +75,11 @@ class WebApp(ApplicationABC):
|
||||
self._logger.debug(f"Allowed origins: {origins}")
|
||||
return origins.split(",")
|
||||
|
||||
def with_database(self) -> Self:
|
||||
self.with_migrations()
|
||||
self.with_seeders()
|
||||
return self
|
||||
|
||||
def with_app(self, app: Starlette) -> Self:
|
||||
assert app is not None, "app must not be None"
|
||||
assert isinstance(app, Starlette), "app must be an instance of Starlette"
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from .application_builder import ApplicationBuilder
|
||||
from .host import Host
|
||||
|
||||
@@ -2,12 +2,10 @@ from abc import ABC, abstractmethod
|
||||
from typing import Callable, Self
|
||||
|
||||
from cpl.application.host import Host
|
||||
from cpl.core.errors import module_dependency_error
|
||||
from cpl.core.log.log_level import LogLevel
|
||||
from cpl.core.log.log_settings import LogSettings
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
from cpl.dependency.typing import TModule
|
||||
|
||||
|
||||
def __not_implemented__(package: str, func: Callable):
|
||||
@@ -22,6 +20,17 @@ class ApplicationABC(ABC):
|
||||
Contains instances of prepared objects
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, services: ServiceProvider, required_modules: list[str | object] = None):
|
||||
self._services = services
|
||||
self._required_modules = (
|
||||
[x.__name__ if not isinstance(x, str) else x for x in required_modules] if required_modules else []
|
||||
)
|
||||
|
||||
@property
|
||||
def required_modules(self) -> list[str]:
|
||||
return self._required_modules
|
||||
|
||||
@classmethod
|
||||
def extend(cls, name: str | Callable, func: Callable[[Self], Self]):
|
||||
r"""Extend the Application with a custom method
|
||||
@@ -38,30 +47,6 @@ class ApplicationABC(ABC):
|
||||
setattr(cls, name, func)
|
||||
return cls
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self, services: ServiceProvider, loaded_modules: set[TModule], required_modules: list[str | object] = None
|
||||
):
|
||||
self._services = services
|
||||
self._modules = loaded_modules
|
||||
self._required_modules = (
|
||||
[x.__name__ if not isinstance(x, str) else x for x in required_modules] if required_modules else []
|
||||
)
|
||||
|
||||
def validate_app_required_modules(self):
|
||||
modules_names = {x.__name__ for x in self._modules}
|
||||
for module in self._required_modules:
|
||||
if module in modules_names:
|
||||
continue
|
||||
|
||||
module_dependency_error(
|
||||
type(self).__name__,
|
||||
module.__name__,
|
||||
ImportError(
|
||||
f"Required module '{module}' for application '{self.__class__.__name__}' is not loaded. Load using 'add_module({module})' method."
|
||||
),
|
||||
)
|
||||
|
||||
def with_logging(self, level: LogLevel = None):
|
||||
if level is None:
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
@@ -72,21 +57,14 @@ class ApplicationABC(ABC):
|
||||
logger = self._services.get_service(LoggerABC)
|
||||
logger.set_level(level)
|
||||
|
||||
def with_permissions(self, *args):
|
||||
try:
|
||||
from cpl.auth import AuthModule
|
||||
def with_permissions(self, *args, **kwargs):
|
||||
__not_implemented__("cpl-auth", self.with_permissions)
|
||||
|
||||
AuthModule.with_permissions(*args)
|
||||
except ImportError:
|
||||
__not_implemented__("cpl-auth", self.with_permissions)
|
||||
def with_migrations(self, *args, **kwargs):
|
||||
__not_implemented__("cpl-database", self.with_migrations)
|
||||
|
||||
def with_migrations(self, *args):
|
||||
try:
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
|
||||
DatabaseModule.with_migrations(self._services, *args)
|
||||
except ImportError:
|
||||
__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
|
||||
@@ -106,17 +84,9 @@ class ApplicationABC(ABC):
|
||||
Called by custom Application.main
|
||||
"""
|
||||
try:
|
||||
for module in self._modules:
|
||||
if not hasattr(module, "configure") and not callable(getattr(module, "configure")):
|
||||
continue
|
||||
module.configure(self._services)
|
||||
|
||||
Host.run_app(self.main)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
logger = self._services.get_service(LoggerABC)
|
||||
logger.info("Application shutdown")
|
||||
|
||||
@abstractmethod
|
||||
def main(self): ...
|
||||
|
||||
@@ -6,6 +6,7 @@ from cpl.application.abc.application_extension_abc import ApplicationExtensionAB
|
||||
from cpl.application.abc.startup_abc import StartupABC
|
||||
from cpl.application.abc.startup_extension_abc import StartupExtensionABC
|
||||
from cpl.application.host import Host
|
||||
from cpl.core.errors import dependency_error
|
||||
from cpl.dependency.context import get_provider, use_root_provider
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
@@ -42,6 +43,18 @@ class ApplicationBuilder(Generic[TApp]):
|
||||
|
||||
return provider
|
||||
|
||||
def validate_app_required_modules(self, app: ApplicationABC):
|
||||
for module in app.required_modules:
|
||||
if module in self._services.loaded_modules:
|
||||
continue
|
||||
|
||||
dependency_error(
|
||||
module,
|
||||
ImportError(
|
||||
f"Required module '{module}' for application '{app.__class__.__name__}' is not loaded. Load using 'add_module({module})' method."
|
||||
),
|
||||
)
|
||||
|
||||
def with_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder":
|
||||
self._startup = startup
|
||||
return self
|
||||
@@ -69,7 +82,6 @@ class ApplicationBuilder(Generic[TApp]):
|
||||
for extension in self._app_extensions:
|
||||
Host.run(extension.run, self.service_provider)
|
||||
|
||||
use_root_provider(self._services.build())
|
||||
app = self._app(self.service_provider, self._services.loaded_modules)
|
||||
app.validate_app_required_modules()
|
||||
app = self._app(self.service_provider)
|
||||
self.validate_app_required_modules(app)
|
||||
return app
|
||||
|
||||
@@ -51,25 +51,33 @@ class Host:
|
||||
|
||||
@classmethod
|
||||
def run_app(cls, func: Callable, *args, **kwargs):
|
||||
loop = cls.get_loop()
|
||||
|
||||
cls.run_start_tasks()
|
||||
cls.run_hosted_services()
|
||||
|
||||
async def runner():
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
await func(*args, **kwargs)
|
||||
app_task = asyncio.create_task(func(*args, **kwargs))
|
||||
else:
|
||||
func(*args, **kwargs)
|
||||
loop = asyncio.get_running_loop()
|
||||
app_task = loop.run_in_executor(None, func, *args, **kwargs)
|
||||
|
||||
await asyncio.wait(
|
||||
[app_task, *cls._tasks.values()],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
except (KeyboardInterrupt, asyncio.CancelledError):
|
||||
pass
|
||||
finally:
|
||||
await cls._stop_all()
|
||||
|
||||
cls.get_loop().run_until_complete(runner())
|
||||
loop.run_until_complete(runner())
|
||||
|
||||
@classmethod
|
||||
def run(cls, func: Callable, *args, **kwargs):
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
return cls.get_loop().run_until_complete(func(*args, **kwargs))
|
||||
return cls._loop.run_until_complete(func(*args, **kwargs))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return func(*args, **kwargs)
|
||||
@@ -1,6 +1,84 @@
|
||||
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 .auth_module import AuthModule
|
||||
from .keycloak_settings import KeycloakSettings
|
||||
from cpl.dependency.service_collection import ServiceCollection as _ServiceCollection
|
||||
from .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
|
||||
from .schema._permission.api_key_permission_dao import ApiKeyPermissionDao
|
||||
from .schema._permission.permission_dao import PermissionDao
|
||||
from .schema._permission.role_dao import RoleDao
|
||||
from .schema._permission.role_permission_dao import RolePermissionDao
|
||||
from .schema._permission.role_user_dao import RoleUserDao
|
||||
|
||||
collection.add_singleton(AuthUserDao)
|
||||
collection.add_singleton(ApiKeyDao)
|
||||
collection.add_singleton(ApiKeyPermissionDao)
|
||||
collection.add_singleton(PermissionDao)
|
||||
collection.add_singleton(RoleDao)
|
||||
collection.add_singleton(RolePermissionDao)
|
||||
collection.add_singleton(RoleUserDao)
|
||||
|
||||
|
||||
def add_auth(collection: _ServiceCollection):
|
||||
import os
|
||||
|
||||
try:
|
||||
from cpl.database.service.migration_service import MigrationService
|
||||
from cpl.database.model.server_type import ServerType, ServerTypes
|
||||
|
||||
collection.add_singleton(_KeycloakClient)
|
||||
collection.add_singleton(_KeycloakAdmin)
|
||||
|
||||
_add_daos(collection)
|
||||
|
||||
provider = collection.build()
|
||||
migration_service: MigrationService = provider.get_service(MigrationService)
|
||||
if ServerType.server_type == ServerTypes.POSTGRES:
|
||||
migration_service.with_directory(
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)), "scripts/postgres")
|
||||
)
|
||||
elif ServerType.server_type == ServerTypes.MYSQL:
|
||||
migration_service.with_directory(os.path.join(os.path.dirname(os.path.realpath(__file__)), "scripts/mysql"))
|
||||
except ImportError as e:
|
||||
from cpl.core.console import Console
|
||||
|
||||
Console.error("cpl-database is not installed", str(e))
|
||||
|
||||
|
||||
def add_permission(collection: _ServiceCollection):
|
||||
from .permission_seeder import PermissionSeeder
|
||||
from .permission.permissions_registry import PermissionsRegistry
|
||||
from .permission.permissions import Permissions
|
||||
|
||||
try:
|
||||
from cpl.database.abc.data_seeder_abc import DataSeederABC
|
||||
|
||||
collection.add_singleton(DataSeederABC, PermissionSeeder)
|
||||
PermissionsRegistry.with_enum(Permissions)
|
||||
except ImportError as e:
|
||||
from cpl.core.console import Console
|
||||
|
||||
Console.error("cpl-database is not installed", str(e))
|
||||
|
||||
|
||||
_ServiceCollection.with_module(add_auth, __name__)
|
||||
_ServiceCollection.with_module(add_permission, _permission.__name__)
|
||||
_ApplicationABC.extend(_ApplicationABC.with_permissions, _with_permissions)
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import Type
|
||||
|
||||
from cpl.auth.keycloak_settings import KeycloakSettings
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.database.model.server_type import ServerType, ServerTypes
|
||||
from cpl.database.mysql.mysql_module import MySQLModule
|
||||
from cpl.database.postgres.postgres_module import PostgresModule
|
||||
from cpl.dependency.module.module import Module
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
from .keycloak.keycloak_admin import KeycloakAdmin
|
||||
from .keycloak.keycloak_client import KeycloakClient
|
||||
from .schema._administration.api_key_dao import ApiKeyDao
|
||||
from .schema._administration.auth_user_dao import AuthUserDao
|
||||
from .schema._permission.api_key_permission_dao import ApiKeyPermissionDao
|
||||
from .schema._permission.permission_dao import PermissionDao
|
||||
from .schema._permission.role_dao import RoleDao
|
||||
from .schema._permission.role_permission_dao import RolePermissionDao
|
||||
from .schema._permission.role_user_dao import RoleUserDao
|
||||
|
||||
|
||||
class AuthModule(Module):
|
||||
dependencies = [DatabaseModule, (MySQLModule, PostgresModule)]
|
||||
config = [KeycloakSettings]
|
||||
singleton = [
|
||||
KeycloakClient,
|
||||
KeycloakAdmin,
|
||||
AuthUserDao,
|
||||
ApiKeyDao,
|
||||
ApiKeyPermissionDao,
|
||||
PermissionDao,
|
||||
RoleDao,
|
||||
RolePermissionDao,
|
||||
RoleUserDao,
|
||||
]
|
||||
scoped = []
|
||||
transient = []
|
||||
|
||||
@staticmethod
|
||||
def configure(provider: ServiceProvider):
|
||||
paths = {
|
||||
ServerTypes.POSTGRES: "scripts/postgres",
|
||||
ServerTypes.MYSQL: "scripts/mysql",
|
||||
}
|
||||
|
||||
DatabaseModule.with_migrations(
|
||||
provider, str(os.path.join(os.path.dirname(os.path.realpath(__file__)), paths[ServerType.server_type]))
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def with_permissions(*permissions: Type[Enum]):
|
||||
from cpl.auth.permission.permissions_registry import PermissionsRegistry
|
||||
|
||||
for perm in permissions:
|
||||
PermissionsRegistry.with_enum(perm)
|
||||
@@ -1,4 +0,0 @@
|
||||
from .permission_module import PermissionsModule
|
||||
from .permission_seeder import PermissionSeeder
|
||||
from .permissions import Permissions
|
||||
from .permissions_registry import PermissionsRegistry
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from cpl.auth.auth_module import AuthModule
|
||||
from cpl.auth.permission.permission_seeder import PermissionSeeder
|
||||
from cpl.auth.permission.permissions import Permissions
|
||||
from cpl.auth.permission.permissions_registry import PermissionsRegistry
|
||||
from cpl.database.abc.data_seeder_abc import DataSeederABC
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.dependency.module.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class PermissionsModule(Module):
|
||||
dependencies = [DatabaseModule, AuthModule]
|
||||
singleton = [(DataSeederABC, PermissionSeeder)]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
PermissionsRegistry.with_enum(Permissions)
|
||||
@@ -3,25 +3,13 @@ import traceback
|
||||
from cpl.core.console import Console
|
||||
|
||||
|
||||
def dependency_error(src: str, package_name: str, e: ImportError = None) -> None:
|
||||
Console.error(f"'{package_name}' is required to use feature: {src}. Please install it and try again.")
|
||||
def dependency_error(package_name: str, e: ImportError) -> None:
|
||||
Console.error(f"'{package_name}' is required to use this feature. Please install it and try again.")
|
||||
tb = traceback.format_exc()
|
||||
if not tb.startswith("NoneType: None"):
|
||||
Console.error("->", tb)
|
||||
Console.write_line("->", tb)
|
||||
|
||||
elif e is not None:
|
||||
Console.error(f"-> {str(e)}")
|
||||
|
||||
exit(1)
|
||||
|
||||
|
||||
def module_dependency_error(src: str, module: str, e: ImportError = None) -> None:
|
||||
Console.error(f"'{module}' is required by '{src}'. Please initialize it with `add_module({module})`.")
|
||||
tb = traceback.format_exc()
|
||||
if not tb.startswith("NoneType: None"):
|
||||
Console.error("->", tb)
|
||||
|
||||
elif e is not None:
|
||||
Console.error(f"-> {str(e)}")
|
||||
Console.write_line("->", str(e))
|
||||
|
||||
exit(1)
|
||||
|
||||
@@ -93,13 +93,14 @@ class Logger(LoggerABC):
|
||||
def _log(self, level: LogLevel, *messages: Messages):
|
||||
try:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
formatted_message = self._format_message(level.value, timestamp, *messages)
|
||||
|
||||
self._write_log_to_file(level, self._file_format_message(level.value, timestamp, *messages))
|
||||
self._write_to_console(level, self._console_format_message(level.value, timestamp, *messages))
|
||||
self._write_log_to_file(level, formatted_message)
|
||||
self._write_to_console(level, formatted_message)
|
||||
except Exception as e:
|
||||
print(f"Error while logging: {e} -> {traceback.format_exc()}")
|
||||
|
||||
def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
if isinstance(messages, tuple):
|
||||
messages = list(messages)
|
||||
|
||||
@@ -118,24 +119,6 @@ class Logger(LoggerABC):
|
||||
|
||||
return message
|
||||
|
||||
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
if isinstance(messages, tuple):
|
||||
messages = list(messages)
|
||||
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
messages = [str(message) for message in messages if message is not None]
|
||||
|
||||
message = f"[{level.upper():^3}]"
|
||||
message += f" [{self._file_prefix}]"
|
||||
if self._source is not None:
|
||||
message += f" - [{self._source}]"
|
||||
|
||||
message += f": {' '.join(messages)}"
|
||||
|
||||
return message
|
||||
|
||||
def header(self, string: str):
|
||||
self._log(LogLevel.info, string)
|
||||
|
||||
|
||||
@@ -11,10 +11,7 @@ class LoggerABC(ABC):
|
||||
def set_level(self, level: LogLevel): ...
|
||||
|
||||
@abstractmethod
|
||||
def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str: ...
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def header(self, string: str):
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import json
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from starlette.requests import Request
|
||||
|
||||
from cpl.core.log.log_level import LogLevel
|
||||
from cpl.core.log.logger import Logger
|
||||
from cpl.core.typing import Source, Messages
|
||||
from cpl.dependency.context import get_provider
|
||||
from cpl.dependency import get_provider
|
||||
|
||||
|
||||
class StructuredLogger(Logger):
|
||||
@@ -19,7 +21,18 @@ class StructuredLogger(Logger):
|
||||
def log_file(self):
|
||||
return f"logs/{self._file_prefix}_{datetime.now().strftime('%Y-%m-%d')}.jsonl"
|
||||
|
||||
def _file_format_message(self, level: str, timestamp: str, *messages: Messages) -> str:
|
||||
def _log(self, level: LogLevel, *messages: Messages):
|
||||
try:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
formatted_message = self._format_message(level.value, timestamp, *messages)
|
||||
structured_message = self._get_structured_message(level.value, timestamp, formatted_message)
|
||||
|
||||
self._write_log_to_file(level, structured_message)
|
||||
self._write_to_console(level, formatted_message)
|
||||
except Exception as e:
|
||||
print(f"Error while logging: {e} -> {traceback.format_exc()}")
|
||||
|
||||
def _get_structured_message(self, level: str, timestamp: str, messages: str) -> str:
|
||||
structured_message = {
|
||||
"timestamp": timestamp,
|
||||
"level": level.upper(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import inspect
|
||||
from typing import Type
|
||||
|
||||
from cpl.core.log import LoggerABC, LogLevel, StructuredLogger
|
||||
from cpl.core.log import LoggerABC, LogLevel
|
||||
from cpl.core.typing import Messages
|
||||
from cpl.dependency.inject import inject
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
@@ -31,11 +31,8 @@ class WrappedLogger(LoggerABC):
|
||||
def set_level(self, level: LogLevel):
|
||||
self._logger.set_level(level)
|
||||
|
||||
def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
return self._logger._file_format_message(level, timestamp, *messages)
|
||||
|
||||
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
return self._logger._console_format_message(level, timestamp, *messages)
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
return self._logger._format_message(level, timestamp, *messages)
|
||||
|
||||
@staticmethod
|
||||
def _get_source() -> str | None:
|
||||
@@ -51,7 +48,6 @@ class WrappedLogger(LoggerABC):
|
||||
ServiceCollection,
|
||||
WrappedLogger,
|
||||
WrappedLogger.__subclasses__(),
|
||||
StructuredLogger,
|
||||
]
|
||||
|
||||
ignore_modules = [x.__module__ for x in ignore_classes if isinstance(x, type)]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from .time_format_settings import TimeFormatSettings
|
||||
from .cron import Cron
|
||||
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
import croniter
|
||||
|
||||
|
||||
class Cron:
|
||||
def __init__(self, cron_expression: str, start_time: datetime = None):
|
||||
self._cron_expression = cron_expression
|
||||
self._start_time = start_time or datetime.now()
|
||||
self._iter = croniter.croniter(cron_expression, self._start_time)
|
||||
|
||||
def next(self) -> datetime:
|
||||
return self._iter.get_next(datetime)
|
||||
@@ -13,7 +13,7 @@ class TimeFormatSettings(ConfigurationModelABC):
|
||||
date_time_format: str = None,
|
||||
date_time_log_format: str = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self, readonly=False)
|
||||
ConfigurationModelABC.__init__(self)
|
||||
self._date_format: Optional[str] = date_format
|
||||
self._time_format: Optional[str] = time_format
|
||||
self._date_time_format: Optional[str] = date_time_format
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user