Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions py_spring_core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from py_spring_core.core.application.py_spring_application import PySpringApplication
from py_spring_core.core.entities.bean_collection import BeanCollection
from py_spring_core.core.entities.component import Component, ComponentScope
from py_spring_core.core.entities.bean_collection.bean_collection import BeanCollection
from py_spring_core.core.entities.component.component import Component, ComponentScope
from py_spring_core.core.entities.controllers.rest_controller import RestController
from py_spring_core.core.entities.controllers.route_mapping import (
DeleteMapping,
Expand All @@ -9,7 +9,7 @@
PostMapping,
PutMapping,
)
from py_spring_core.core.entities.entity_provider import EntityProvider
from py_spring_core.core.entities.entity_provider.entity_provider import EntityProvider
from py_spring_core.core.entities.middlewares.middleware import Middleware
from py_spring_core.core.entities.middlewares.middleware_registry import (
MiddlewareRegistry,
Expand All @@ -22,7 +22,7 @@
from py_spring_core.event.application_event_publisher import ApplicationEventPublisher
from py_spring_core.event.commons import ApplicationEvent

__version__ = "0.0.23"
__version__ = "0.0.24"

__all__ = [
"PySpringApplication",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"properties_file_path": "./application-properties.json",
"loguru_config": {"log_file_path": "./logs/app.log", "log_level": "DEBUG"},
"type_checking_mode": "strict",
"shutdown_config": {"timeout_seconds": 30.0, "enabled": True},
}

app_properties_template: dict[str, Any] = {}
15 changes: 15 additions & 0 deletions py_spring_core/core/application/application_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ class ServerConfig(BaseModel):
enabled: bool = Field(default=True)


class ShutdownConfig(BaseModel):
"""
Represents the configuration for graceful shutdown.

Attributes:
timeout_seconds: The maximum time in seconds to wait for graceful shutdown before forcing termination.
enabled: A boolean flag indicating whether graceful shutdown timeout is enabled.
"""

timeout_seconds: float = Field(default=30.0, description="Timeout in seconds for graceful shutdown")
enabled: bool = Field(default=True, description="Whether graceful shutdown timeout is enabled")


class ApplicationConfig(BaseModel):
"""
Represents the configuration for the application.
Expand All @@ -31,6 +44,7 @@ class ApplicationConfig(BaseModel):
sqlalchemy_database_uri: The URI for the SQLAlchemy database connection.
properties_file_path: The file path for the application properties.
model_file_postfix_patterns: A list of file name patterns for model (for table creation) files.
shutdown_config: The configuration for graceful shutdown.
"""

model_config = ConfigDict(protected_namespaces=())
Expand All @@ -40,6 +54,7 @@ class ApplicationConfig(BaseModel):
server_config: ServerConfig
properties_file_path: str
loguru_config: LoguruConfig
shutdown_config: ShutdownConfig = Field(default_factory=ShutdownConfig)


class ApplicationConfigRepository(JsonConfigRepository[ApplicationConfig]):
Expand Down
4 changes: 2 additions & 2 deletions py_spring_core/core/application/commons.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from py_spring_core.core.entities.bean_collection import BeanCollection
from py_spring_core.core.entities.component import Component
from py_spring_core.core.entities.bean_collection.bean_collection import BeanCollection
from py_spring_core.core.entities.component.component import Component
from py_spring_core.core.entities.controllers.rest_controller import RestController
from py_spring_core.core.entities.properties.properties import Properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
from py_spring_core.core.application.context.application_context_config import (
ApplicationContextConfig,
)
from py_spring_core.core.entities.bean_collection import (
from py_spring_core.core.entities.bean_collection.bean_collection import (
BeanCollection,
BeanConflictError,
BeanView,
InvalidBeanError,
)
from py_spring_core.core.entities.component import Component, ComponentScope
from py_spring_core.core.entities.component.component import Component, ComponentScope
from py_spring_core.core.entities.controllers.rest_controller import RestController
from py_spring_core.core.entities.entity_provider import EntityProvider
from py_spring_core.core.entities.entity_provider.entity_provider import EntityProvider
from py_spring_core.core.entities.properties.properties import Properties
from py_spring_core.core.entities.properties.properties_loader import _PropertiesLoader

Expand Down
91 changes: 74 additions & 17 deletions py_spring_core/core/application/py_spring_application.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import os
from typing import Any, Callable, Iterable, Type
from typing import Any, Callable, Iterable, Optional, Type

import uvicorn
from fastapi import APIRouter, FastAPI
Expand All @@ -23,23 +23,27 @@
ApplicationContextConfig,
)
from py_spring_core.core.application.loguru_config import LogFormat
from py_spring_core.core.entities.bean_collection import BeanCollection
from py_spring_core.core.entities.component import Component, ComponentLifeCycle
from py_spring_core.core.entities.bean_collection.bean_collection import BeanCollection
from py_spring_core.core.entities.component.component import Component, ComponentLifeCycle
from py_spring_core.core.entities.controllers.rest_controller import RestController
from py_spring_core.core.entities.controllers.route_mapping import RouteMapping
from py_spring_core.core.entities.entity_provider import EntityProvider
from py_spring_core.core.entities.entity_provider.entity_provider import EntityProvider
from py_spring_core.core.entities.middlewares.middleware import Middleware
from py_spring_core.core.entities.middlewares.middleware_registry import (
MiddlewareRegistry,
)
from py_spring_core.core.entities.properties.properties import Properties
from py_spring_core.core.interfaces.application_context_required import (
ApplicationContextRequired,
)
from py_spring_core.core.interfaces.graceful_shutdown_handler import GracefulShutdownHandler
from py_spring_core.core.interfaces.single_inheritance_required import SingleInheritanceRequired
from py_spring_core.event.application_event_handler_registry import (
ApplicationEventHandlerRegistry,
)
from py_spring_core.event.application_event_publisher import ApplicationEventPublisher

import py_spring_core.core.utils as framework_utils

class PySpringApplication:
"""
Expand Down Expand Up @@ -102,6 +106,7 @@ def __init__(
self.type_checking_service = TypeCheckingService(
self.app_config.app_src_target_dir
)
self.shutdown_handler: Optional[GracefulShutdownHandler] = None

def __configure_logging(self):
"""Applies the logging configuration using Loguru."""
Expand Down Expand Up @@ -232,31 +237,76 @@ def __init_controllers(self) -> None:
self.fastapi.include_router(router)
logger.debug(f"[CONTROLLER INIT] Controller {name} initialized")

def __init_middlewares(self) -> None:
logger.debug("[MIDDLEWARE INIT] Initialize middlewares...")
self_defined_registry_cls = MiddlewareRegistry.get_subclass()
if self_defined_registry_cls is None:
logger.debug("[MIDDLEWARE INIT] No self defined registry class found")
return
def _init_external_handler(self, base_class: Type[SingleInheritanceRequired]) -> Type[Any] | None:
"""Initialize an external handler (middleware registry or graceful shutdown handler).

Args:
base_class: The base class to get subclass from
handler_type: The type of handler for logging purposes

Returns:
The initialized handler class or None if no handler is found

Raises:
RuntimeError: If the handler has unimplemented abstract methods
"""
handler_type = base_class.__name__
self_defined_handler_cls = base_class.get_subclass()
if self_defined_handler_cls is None:
logger.debug(f"[{handler_type} INIT] No self defined {handler_type.lower()} class found")
return None

unimplemented_abstract_methods = framework_utils.get_unimplemented_abstract_methods(self_defined_handler_cls)
if len(unimplemented_abstract_methods) > 0:
error_message = f"[{handler_type} INIT] Self defined {handler_type.lower()} class: {self_defined_handler_cls.__name__} has unimplemented abstract methods: {unimplemented_abstract_methods}"
logger.error(error_message)
raise RuntimeError(error_message)

logger.debug(
f"[MIDDLEWARE INIT] Self defined registry class: {self_defined_registry_cls.__name__}"
f"[{handler_type} INIT] Self defined {handler_type.lower()} class: {self_defined_handler_cls.__name__}"
)
logger.debug(
f"[MIDDLEWARE INIT] Inject dependencies for external object: {self_defined_registry_cls.__name__}"
f"[{handler_type} INIT] Inject dependencies for external object: {self_defined_handler_cls.__name__}"
)
self.app_context.inject_dependencies_for_external_object(
self_defined_registry_cls
self_defined_handler_cls
)
registry = self_defined_registry_cls()
return self_defined_handler_cls

middleware_classes = registry.get_middleware_classes()
def __init_middlewares(self) -> None:
handler_type = MiddlewareRegistry.__name__
logger.debug(f"[{handler_type} INIT] Initialize middlewares...")
registry_cls = self._init_external_handler(MiddlewareRegistry)
if registry_cls is None:
return

registry: MiddlewareRegistry = registry_cls()
middleware_classes: list[Type[Middleware]] = registry.get_middleware_classes()
for middleware_class in middleware_classes:
logger.debug(
f"[MIDDLEWARE INIT] Inject dependencies for middleware: {middleware_class.__name__}"
f"[{handler_type} INIT] Inject dependencies for middleware: {middleware_class.__name__}"
)
self.app_context.inject_dependencies_for_external_object(middleware_class)
registry.apply_middlewares(self.fastapi)
logger.debug("[MIDDLEWARE INIT] Middlewares initialized")
logger.debug(f"[{handler_type} INIT] Middlewares initialized")

def __init_graceful_shutdown(self) -> None:
handler_type = GracefulShutdownHandler.__name__
logger.debug(f"[{handler_type} INIT] Initialize graceful shutdown...")
handler_cls: Optional[Type[GracefulShutdownHandler]] = self._init_external_handler(GracefulShutdownHandler)
if handler_cls is None:
return

# Get shutdown configuration
shutdown_config = self.app_config.shutdown_config
logger.debug(f"[{handler_type} INIT] Shutdown timeout: {shutdown_config.timeout_seconds}s, enabled: {shutdown_config.enabled}")

# Initialize handler with timeout configuration
self.shutdown_handler = handler_cls(
timeout_seconds=shutdown_config.timeout_seconds,
timeout_enabled=shutdown_config.enabled
)
logger.debug(f"[{handler_type} INIT] Graceful shutdown initialized")

def __configure_uvicorn_logging(self):
"""Configure Uvicorn to use Loguru instead of default logging."""
Expand Down Expand Up @@ -300,7 +350,14 @@ def run(self) -> None:
self.__init_app()
self.__init_controllers()
self.__init_middlewares()
self.__init_graceful_shutdown()
if self.app_config.server_config.enabled:
self.__run_server()
finally:
# Handle component lifecycle destruction
self._handle_singleton_components_life_cycle(ComponentLifeCycle.Destruction)
# Handle graceful shutdown completion
if self.shutdown_handler:
self.shutdown_handler.complete_shutdown()


Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from typing import Any, Optional, Type

from py_spring_core.core.application.commons import AppEntities
from py_spring_core.core.entities.bean_collection import BeanCollection
from py_spring_core.core.entities.component import Component
from py_spring_core.core.entities.bean_collection.bean_collection import BeanCollection
from py_spring_core.core.entities.component.component import Component
from py_spring_core.core.entities.controllers.rest_controller import RestController
from py_spring_core.core.entities.properties.properties import Properties

Expand Down
Loading