pyboot-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyboot/__init__.py +6 -0
- pyboot/core/__init__.py +0 -0
- pyboot/core/application.py +162 -0
- pyboot/core/builtins/BuiltInAttr.py +8 -0
- pyboot/core/builtins/__init__.py +0 -0
- pyboot/core/builtins/component.py +58 -0
- pyboot/core/builtins/configuration.py +97 -0
- pyboot/core/builtins/py.typed +0 -0
- pyboot/core/exceptions.py +72 -0
- pyboot/core/ioc.py +119 -0
- pyboot/core/plugin_loader.py +30 -0
- pyboot/core/pyboot_context.py +16 -0
- pyboot/core/pyboot_plugin.py +52 -0
- pyboot/core/types.py +7 -0
- pyboot/py.typed +0 -0
- pyboot_framework-0.1.0.dist-info/METADATA +571 -0
- pyboot_framework-0.1.0.dist-info/RECORD +20 -0
- pyboot_framework-0.1.0.dist-info/WHEEL +5 -0
- pyboot_framework-0.1.0.dist-info/entry_points.txt +3 -0
- pyboot_framework-0.1.0.dist-info/top_level.txt +1 -0
pyboot/__init__.py
ADDED
pyboot/core/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from .pyboot_context import PyBootContext
|
|
2
|
+
from .pyboot_plugin import PyBootPlugin
|
|
3
|
+
from .types import *
|
|
4
|
+
from .plugin_loader import PluginLoader
|
|
5
|
+
from .exceptions import *
|
|
6
|
+
|
|
7
|
+
import importlib
|
|
8
|
+
import pkgutil
|
|
9
|
+
import inspect
|
|
10
|
+
|
|
11
|
+
from typing import TypeVar,Generic
|
|
12
|
+
|
|
13
|
+
from loguru import logger
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
#PyBoot Application Class
|
|
18
|
+
class PyBoot(Generic[T]):
|
|
19
|
+
"""
|
|
20
|
+
This class represents the core of the PyBoot framework. It is responsible for bootstrapping the application, discovering classes and plugins, and managing the application context.It is the main entry point for initializing and running a PyBoot application. It provides methods to scan for classes in a specified package, load plugins, and execute the lifecycle hooks defined by the plugins.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self,app_instance:T,debug:bool=False) -> None:
|
|
24
|
+
self.plugin_loader:PluginLoader=PluginLoader()
|
|
25
|
+
self.context:PyBootContext=PyBootContext(app=app_instance)
|
|
26
|
+
|
|
27
|
+
if debug:
|
|
28
|
+
logger.enable("pyboot")
|
|
29
|
+
else:
|
|
30
|
+
logger.disable("pyboot")
|
|
31
|
+
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def get_app(self)->T:
|
|
35
|
+
"""
|
|
36
|
+
Returns the application instance.This is useful for accessing the main application object from within plugins or other components of the framework.Instance of the application can be retrieved using this method, allowing for interaction with the application's state and behavior.
|
|
37
|
+
"""
|
|
38
|
+
return self.context.app
|
|
39
|
+
|
|
40
|
+
#scan all global level classes in the package
|
|
41
|
+
def scan_classes(self, package_name: str):
|
|
42
|
+
"""
|
|
43
|
+
Scans the specified package for all classes defined within it. This method uses Python's importlib and pkgutil modules to dynamically discover and import modules within the given package, and then inspects each module to find classes. It returns a set of all discovered classes.
|
|
44
|
+
"""
|
|
45
|
+
logger.debug(f"Scanning package:{package_name} for classes...")
|
|
46
|
+
|
|
47
|
+
classes:set[ClassTBase]=set()
|
|
48
|
+
|
|
49
|
+
logger.info(f"Importing package:{package_name}...")
|
|
50
|
+
package = importlib.import_module(name=package_name)
|
|
51
|
+
logger.info(f"Package:{package_name} imported successfully.")
|
|
52
|
+
|
|
53
|
+
for module_info in pkgutil.walk_packages(
|
|
54
|
+
path=package.__path__, prefix=f"{package.__name__}."
|
|
55
|
+
):
|
|
56
|
+
|
|
57
|
+
logger.debug(f"Importing module:{module_info.name}...")
|
|
58
|
+
module = importlib.import_module(module_info.name)
|
|
59
|
+
logger.debug(f"Module:{module_info.name} imported successfully.")
|
|
60
|
+
|
|
61
|
+
for _, clazz in inspect.getmembers(module, predicate=inspect.isclass):
|
|
62
|
+
|
|
63
|
+
# skip imported modules
|
|
64
|
+
if clazz.__module__ != module.__name__:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
logger.debug(f"Discovered class:{clazz.__name__} in module:{module_info.name}.")
|
|
68
|
+
classes.add(clazz)
|
|
69
|
+
logger.info(f"Class:{clazz.__name__} added to discovered classes.")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
return classes
|
|
73
|
+
|
|
74
|
+
#boot strap the application
|
|
75
|
+
def bootstrap(self,package:str)->None:
|
|
76
|
+
"""
|
|
77
|
+
This method orchestrates the bootstrapping process of the application. It performs several key steps:
|
|
78
|
+
1. Loads plugins using the PluginLoader.
|
|
79
|
+
2. Executes lifecycle hooks defined by the plugins at various stages of the bootstrapping process.
|
|
80
|
+
3. Scans the specified package for classes and registers them in the application context.
|
|
81
|
+
4. Creates class-level beans for all discovered classes.
|
|
82
|
+
5. Executes additional lifecycle hooks after class discovery and bean creation.
|
|
83
|
+
6. Finally, it signals that the application is ready by calling the on_ready hook of each plugin.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
logger.info("Bootstrapping the application...")
|
|
87
|
+
|
|
88
|
+
logger.info(f"Scanning package:{package} for classes and plugins...")
|
|
89
|
+
#Discover and load the plugin objects
|
|
90
|
+
self.plugins:set[PyBootPlugin]=self.plugin_loader.load_plugins()
|
|
91
|
+
logger.debug(f"Discovered Plugins:{self.plugins}")
|
|
92
|
+
logger.info("Running the after_plugin_discovery...")
|
|
93
|
+
#after_plugins_discovery
|
|
94
|
+
for plugin in self.plugins:
|
|
95
|
+
plugin.after_plugins_discovery(context=self.context)
|
|
96
|
+
logger.info("Plugin lifecycle complete!!!")
|
|
97
|
+
|
|
98
|
+
logger.info("Running the before_classes_discovery...")
|
|
99
|
+
#before_classes_discovery
|
|
100
|
+
for plugin in self.plugins:
|
|
101
|
+
plugin.before_classes_discovery(context=self.context)
|
|
102
|
+
logger.info("Discovering classes...")
|
|
103
|
+
#discover
|
|
104
|
+
self.context.discovered_classes=self.scan_classes(package_name=package)
|
|
105
|
+
logger.info(f"Discovered classes:{self.context.discovered_classes}")
|
|
106
|
+
logger.info("Running the after_classes_discovery...")
|
|
107
|
+
#after_classes_discovery
|
|
108
|
+
for plugin in self.plugins:
|
|
109
|
+
plugin.after_classes_discovery(context=self.context)
|
|
110
|
+
logger.info("Class discovery lifecycle complete!!!")
|
|
111
|
+
|
|
112
|
+
logger.info("Running the before_register...")
|
|
113
|
+
#before_register
|
|
114
|
+
for plugin in self.plugins:
|
|
115
|
+
plugin.before_register(context=self.context)
|
|
116
|
+
logger.info("before_register lifecycle complete!!!")
|
|
117
|
+
|
|
118
|
+
logger.info("Running the on_register...")
|
|
119
|
+
#on_register
|
|
120
|
+
for plugin in self.plugins:
|
|
121
|
+
plugin.on_register(context=self.context)
|
|
122
|
+
logger.info("on_register lifecycle complete!!!")
|
|
123
|
+
|
|
124
|
+
logger.info("Running the after_register...")
|
|
125
|
+
#after_register
|
|
126
|
+
for plugin in self.plugins:
|
|
127
|
+
plugin.after_register(context=self.context)
|
|
128
|
+
logger.info("after_register lifecycle complete!!!")
|
|
129
|
+
|
|
130
|
+
logger.info("Creating class level beans...")
|
|
131
|
+
#create class level beans only beans
|
|
132
|
+
for clazz in self.context.ioc.definitions:
|
|
133
|
+
self.context.ioc.get_bean(clazz=clazz)
|
|
134
|
+
logger.info("Class level beans created successfully!!!")
|
|
135
|
+
|
|
136
|
+
logger.info("Running the before_load...")
|
|
137
|
+
#before_load
|
|
138
|
+
for plugin in self.plugins:
|
|
139
|
+
plugin.before_load(context=self.context)
|
|
140
|
+
logger.info("before_load lifecycle complete!!!")
|
|
141
|
+
|
|
142
|
+
logger.info("Running the on_load...")
|
|
143
|
+
#on_load
|
|
144
|
+
for plugin in self.plugins:
|
|
145
|
+
plugin.on_load(context=self.context)
|
|
146
|
+
logger.info("on_load lifecycle complete!!!")
|
|
147
|
+
|
|
148
|
+
logger.info("Running the after_load...")
|
|
149
|
+
#after_load
|
|
150
|
+
for plugin in self.plugins:
|
|
151
|
+
plugin.after_load(context=self.context)
|
|
152
|
+
logger.info("after_load lifecycle complete!!!")
|
|
153
|
+
|
|
154
|
+
logger.info("Running the on_ready...")
|
|
155
|
+
#on_ready
|
|
156
|
+
for plugin in self.plugins:
|
|
157
|
+
plugin.on_ready(context=self.context)
|
|
158
|
+
logger.info("on_ready lifecycle complete!!!")
|
|
159
|
+
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pyboot.core.pyboot_context import PyBootContext
|
|
2
|
+
|
|
3
|
+
from .BuiltInAttr import BuiltInAttr
|
|
4
|
+
from ..types import *
|
|
5
|
+
from ..pyboot_plugin import PyBootPlugin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Component:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def __call__(self, clazz: ClassT) -> ClassT:
|
|
13
|
+
setattr(clazz, BuiltInAttr.IS_PYBOOT_COMPONENT_CLASS.value, True)
|
|
14
|
+
return clazz
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ComponentPlugin(PyBootPlugin):
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def after_plugins_discovery(self, context: PyBootContext) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def before_classes_discovery(self, context: PyBootContext) -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def after_classes_discovery(self, context: PyBootContext) -> None:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def before_register(self, context: PyBootContext) -> None:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def on_register(self, context: PyBootContext) -> None:
|
|
35
|
+
for clazz in context.discovered_classes:
|
|
36
|
+
|
|
37
|
+
#skip other classes
|
|
38
|
+
if not getattr(clazz,BuiltInAttr.IS_PYBOOT_COMPONENT_CLASS.value,False):
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
context.ioc.register_class(clazz=clazz)
|
|
42
|
+
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
def after_register(self, context: PyBootContext) -> None:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def before_load(self, context: PyBootContext) -> None:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def on_load(self, context: PyBootContext) -> None:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def after_load(self, context: PyBootContext) -> None:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def on_ready(self, context: PyBootContext) -> None:
|
|
58
|
+
pass
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from ..pyboot_context import PyBootContext
|
|
2
|
+
from .BuiltInAttr import BuiltInAttr
|
|
3
|
+
from ..pyboot_plugin import PyBootPlugin
|
|
4
|
+
from ..types import *
|
|
5
|
+
|
|
6
|
+
import inspect
|
|
7
|
+
from typing import Any, get_type_hints
|
|
8
|
+
|
|
9
|
+
class InvalidBeanFactoryDefinitionException(Exception):
|
|
10
|
+
def __init__(self,message:str) -> None:
|
|
11
|
+
self.message:str=message
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Configuration:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def __call__(self,clazz:ClassT) -> ClassT:
|
|
20
|
+
setattr(clazz,BuiltInAttr.IS_PYBOOT_CONFIGURATION_CLASS.value,True)
|
|
21
|
+
return clazz
|
|
22
|
+
|
|
23
|
+
class Bean:
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def __call__(self,method:Callable[...,Any]) ->Callable[...,Any]:
|
|
28
|
+
setattr(method,BuiltInAttr.IS_PYBOOT_BEAN_FACTORY.value,True)
|
|
29
|
+
return method
|
|
30
|
+
|
|
31
|
+
class ConfigurationPlugin(PyBootPlugin):
|
|
32
|
+
def after_plugins_discovery(self, context: PyBootContext) -> None:
|
|
33
|
+
return super().after_plugins_discovery(context)
|
|
34
|
+
|
|
35
|
+
def before_classes_discovery(self, context: PyBootContext) -> None:
|
|
36
|
+
return super().before_classes_discovery(context)
|
|
37
|
+
|
|
38
|
+
def after_classes_discovery(self, context: PyBootContext) -> None:
|
|
39
|
+
return super().after_classes_discovery(context)
|
|
40
|
+
|
|
41
|
+
def before_register(self, context: PyBootContext) -> None:
|
|
42
|
+
return super().before_register(context)
|
|
43
|
+
|
|
44
|
+
# A Configuration class is a class that contains bean factory methods that produce beans for the application context.Its main responsibility is to define and configure beans that can be used throughout the application.So it must not wait for the application context to be fully initialized before it can register its beans. Instead, it should register its beans during the configuration phase of the application context initialization process.
|
|
45
|
+
def on_register(self, context: PyBootContext) -> None:
|
|
46
|
+
|
|
47
|
+
for clazz in context.discovered_classes:
|
|
48
|
+
|
|
49
|
+
# skip other classes
|
|
50
|
+
if not getattr(clazz, BuiltInAttr.IS_PYBOOT_CONFIGURATION_CLASS.value, False):
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
#register the class
|
|
54
|
+
context.ioc.register_class(clazz=clazz)
|
|
55
|
+
|
|
56
|
+
#immediately get the bean
|
|
57
|
+
config_bean = context.ioc.get_bean(clazz=clazz)
|
|
58
|
+
|
|
59
|
+
for method_name, method in inspect.getmembers(
|
|
60
|
+
config_bean, predicate=inspect.ismethod
|
|
61
|
+
):
|
|
62
|
+
|
|
63
|
+
# skip other methods
|
|
64
|
+
if not getattr(
|
|
65
|
+
method,
|
|
66
|
+
BuiltInAttr.IS_PYBOOT_BEAN_FACTORY.value,
|
|
67
|
+
False,
|
|
68
|
+
):
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# get return type of the bean factory what the factory is producing
|
|
72
|
+
return_type = get_type_hints(method).get("return")
|
|
73
|
+
|
|
74
|
+
# if the factory is producing None , then it is invalid
|
|
75
|
+
if return_type is None:
|
|
76
|
+
raise InvalidBeanFactoryDefinitionException(
|
|
77
|
+
f"@Bean method '{method_name}' must have a return annotation"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# register the bean factory
|
|
81
|
+
context.ioc.register_bean_factory(clazz=return_type, method=method)
|
|
82
|
+
|
|
83
|
+
def after_register(self, context: PyBootContext) -> None:
|
|
84
|
+
return super().after_register(context)
|
|
85
|
+
|
|
86
|
+
def before_load(self, context: PyBootContext) -> None:
|
|
87
|
+
return super().before_load(context)
|
|
88
|
+
|
|
89
|
+
def on_load(self, context: PyBootContext) -> None:
|
|
90
|
+
return super().on_load(context)
|
|
91
|
+
|
|
92
|
+
def after_load(self, context: PyBootContext) -> None:
|
|
93
|
+
return super().after_load(context)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def on_ready(self, context: PyBootContext) -> None:
|
|
97
|
+
return super().on_ready(context)
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#exceptions
|
|
2
|
+
class MultipleBeanException(Exception):
|
|
3
|
+
def __init__(self, message: str) -> None:
|
|
4
|
+
super()
|
|
5
|
+
self.message: str = message
|
|
6
|
+
return
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClassNotRegisteredForBean(Exception):
|
|
10
|
+
def __init__(self, message: str) -> None:
|
|
11
|
+
super()
|
|
12
|
+
self.message: str = message
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DependencyResolutionException(Exception):
|
|
17
|
+
def __init__(self, message: str) -> None:
|
|
18
|
+
super()
|
|
19
|
+
self.message: str = message
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CircularDependencyException(Exception):
|
|
24
|
+
def __init__(self, message: str) -> None:
|
|
25
|
+
super()
|
|
26
|
+
self.message: str = message
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MethodNameExpectedToHaveMethodTypeException(Exception):
|
|
31
|
+
def __init__(self, message: str) -> None:
|
|
32
|
+
super()
|
|
33
|
+
self.message: str = message
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NoHttpMethodDefinedForRoute(Exception):
|
|
38
|
+
def __init__(self, message: str) -> None:
|
|
39
|
+
super()
|
|
40
|
+
self.message: str = message
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
class NoHttpRoutePathDefined(Exception):
|
|
44
|
+
def __init__(self, message: str) -> None:
|
|
45
|
+
super()
|
|
46
|
+
self.message: str = message
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
class NoClassTypeForExceptionHandler(Exception):
|
|
50
|
+
def __init__(self, message: str) -> None:
|
|
51
|
+
super()
|
|
52
|
+
self.message: str = message
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class MultipleClassDefinitionException(Exception):
|
|
57
|
+
def __init__(self, message:str) -> None:
|
|
58
|
+
super()
|
|
59
|
+
self.message:str=message
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
class InvalidBeanFactoryException(Exception):
|
|
63
|
+
def __init__(self, message:str) -> None:
|
|
64
|
+
super()
|
|
65
|
+
self.messae:str=message
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
class ContextFieldNotSetException(Exception):
|
|
69
|
+
def __init__(self, message:str) -> None:
|
|
70
|
+
super()
|
|
71
|
+
self.messae:str=message
|
|
72
|
+
return
|
pyboot/core/ioc.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from .exceptions import *
|
|
2
|
+
from .types import *
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import get_type_hints
|
|
6
|
+
|
|
7
|
+
#IOC Container
|
|
8
|
+
class IocContainer:
|
|
9
|
+
"""The Application Wide Inversion Of Control(IOC) container for managing beans"""
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self.definitions:set[ClassTBase]=set()
|
|
12
|
+
self.singletons:dict[ClassTBase,object]=dict()
|
|
13
|
+
self.bean_factories:dict[ClassTBase,MethodTBase]=dict()
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
def register_class(self,clazz:ClassTBase)->None:
|
|
17
|
+
if clazz in self.definitions:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
self.definitions.add(clazz)
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
def register_bean_factory(self,clazz:ClassTBase,method:MethodTBase)->None:
|
|
24
|
+
if clazz in self.definitions:
|
|
25
|
+
raise MultipleBeanException(f"Multiple bean definitions for {clazz.__name__}. One is already registered by some class")
|
|
26
|
+
|
|
27
|
+
if clazz in self.bean_factories:
|
|
28
|
+
raise MultipleBeanException(f"Multiple bean definitions for {clazz.__name__}")
|
|
29
|
+
|
|
30
|
+
self.bean_factories[clazz]=method
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
def _create_object_from_class(self,clazz:ClassTBase,constructing_object_set:set[ClassTBase]):
|
|
34
|
+
if clazz in constructing_object_set:
|
|
35
|
+
raise CircularDependencyException(f"Encountered circular dependency injection in class:{clazz.__name__}")
|
|
36
|
+
|
|
37
|
+
constructing_object_set.add(clazz)
|
|
38
|
+
|
|
39
|
+
if not "__init__" in clazz.__dict__:
|
|
40
|
+
return clazz()
|
|
41
|
+
|
|
42
|
+
signature=inspect.signature(clazz.__init__)
|
|
43
|
+
type_hints = get_type_hints(clazz.__init__)
|
|
44
|
+
|
|
45
|
+
kwargs:dict[str,object]={}
|
|
46
|
+
|
|
47
|
+
for name,parameter in signature.parameters.items():
|
|
48
|
+
if name =="self":
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
if parameter.default is not inspect.Parameter.empty:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
dependency_type=type_hints.get(name)
|
|
55
|
+
|
|
56
|
+
if dependency_type is None:
|
|
57
|
+
raise DependencyResolutionException(f"Missing type annotation for parameter:{name} in class:{clazz.__name__}")
|
|
58
|
+
|
|
59
|
+
kwargs[name]=self._get_bean(clazz=dependency_type,constructing_object_set=constructing_object_set)
|
|
60
|
+
|
|
61
|
+
constructing_object_set.remove(clazz)
|
|
62
|
+
|
|
63
|
+
print(f"Creating Bean of class:{clazz.__name__} from ioc definition")
|
|
64
|
+
return clazz(**kwargs)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _create_object_from_method(self,bean_type:ClassTBase,method:MethodTBase,constructing_object_set:set[ClassTBase]):
|
|
68
|
+
if bean_type in constructing_object_set:
|
|
69
|
+
raise CircularDependencyException(f"Encountered circular dependency injection in class:{bean_type.__name__}")
|
|
70
|
+
|
|
71
|
+
constructing_object_set.add(bean_type)
|
|
72
|
+
|
|
73
|
+
signature=inspect.signature(method)
|
|
74
|
+
type_hints = get_type_hints(method)
|
|
75
|
+
|
|
76
|
+
kwargs:dict[str,object]={}
|
|
77
|
+
|
|
78
|
+
for name,parameter in signature.parameters.items():
|
|
79
|
+
|
|
80
|
+
#if it is self, skip
|
|
81
|
+
if name =="self":
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
if parameter.default is not inspect.Parameter.empty:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
dependency_type=type_hints.get(name)
|
|
88
|
+
|
|
89
|
+
if dependency_type is None:
|
|
90
|
+
raise DependencyResolutionException(f"Missing type annotation for parameter:{name} in method:{method.__name__}")
|
|
91
|
+
|
|
92
|
+
kwargs[name]=self._get_bean(clazz=dependency_type,constructing_object_set=constructing_object_set)
|
|
93
|
+
|
|
94
|
+
constructing_object_set.remove(bean_type)
|
|
95
|
+
print(f"Creating bean of class:{bean_type} from method:{method.__name__}")
|
|
96
|
+
return method(**kwargs)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_bean(self,clazz:ClassTBase,constructing_object_set:set[ClassTBase])->object:
|
|
100
|
+
if clazz in self.singletons:
|
|
101
|
+
return self.singletons[clazz]
|
|
102
|
+
|
|
103
|
+
if clazz in self.definitions:
|
|
104
|
+
self.singletons[clazz]=self._create_object_from_class(clazz=clazz,constructing_object_set=constructing_object_set)
|
|
105
|
+
elif clazz in self.bean_factories:
|
|
106
|
+
method=self.bean_factories.get(clazz)
|
|
107
|
+
|
|
108
|
+
if method is None:
|
|
109
|
+
raise DependencyResolutionException(f"Missing Factory for bean of class:{clazz.__name__} but somehow reached here!")
|
|
110
|
+
|
|
111
|
+
self.singletons[clazz]=self._create_object_from_method(bean_type=clazz,constructing_object_set=constructing_object_set,method=method)
|
|
112
|
+
else:
|
|
113
|
+
raise ClassNotRegisteredForBean(f"Class not registered for bean of instance:{clazz.__name__}")
|
|
114
|
+
|
|
115
|
+
return self.singletons[clazz]
|
|
116
|
+
|
|
117
|
+
def get_bean(self,clazz:ClassTBase)->object:
|
|
118
|
+
constructing_object_set:set[ClassTBase]=set()
|
|
119
|
+
return self._get_bean(clazz=clazz,constructing_object_set=constructing_object_set)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .pyboot_plugin import PyBootPlugin
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import entry_points
|
|
4
|
+
from .types import ClassTBase
|
|
5
|
+
import inspect
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PluginLoader:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def load_plugins(self)->set[PyBootPlugin]:
|
|
13
|
+
plugins:set[PyBootPlugin]=set()
|
|
14
|
+
|
|
15
|
+
for entry_point in entry_points(group="pyboot.plugins"):
|
|
16
|
+
plugin_class=entry_point.load()
|
|
17
|
+
|
|
18
|
+
if not isinstance(plugin_class,ClassTBase):
|
|
19
|
+
raise TypeError(f"Plugin:{entry_point.name} must reference a {ClassTBase} class")
|
|
20
|
+
|
|
21
|
+
if not issubclass(plugin_class,PyBootPlugin):
|
|
22
|
+
raise TypeError(f"Plugin:{entry_point.name} must extend the {PyBootPlugin} PyBootPlugin")
|
|
23
|
+
|
|
24
|
+
if inspect.isabstract(plugin_class):
|
|
25
|
+
raise TypeError(f"Plugin:{entry_point.name} must not be an abstract class")
|
|
26
|
+
|
|
27
|
+
plugin_instance=plugin_class()
|
|
28
|
+
plugins.add(plugin_instance)
|
|
29
|
+
|
|
30
|
+
return plugins
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .types import *
|
|
2
|
+
from .ioc import IocContainer
|
|
3
|
+
from .exceptions import *
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
#PyBoot Context Class
|
|
10
|
+
class PyBootContext:
|
|
11
|
+
def __init__(self,app:Any) -> None:
|
|
12
|
+
self.discovered_classes:set[ClassTBase]=set()
|
|
13
|
+
self.ioc:IocContainer=IocContainer()
|
|
14
|
+
self.app:Any=app
|
|
15
|
+
return
|
|
16
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from abc import ABC,abstractmethod
|
|
2
|
+
from .pyboot_context import PyBootContext
|
|
3
|
+
|
|
4
|
+
class PyBootPlugin(ABC):
|
|
5
|
+
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def after_plugins_discovery(self,context:PyBootContext)->None:
|
|
8
|
+
"""Runs just after pyboot has discovered all the plugins"""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def before_classes_discovery(self,context:PyBootContext)->None:
|
|
13
|
+
"""Runs just before pyboot begins to discover classes"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def after_classes_discovery(self,context:PyBootContext)->None:
|
|
18
|
+
"""Run just after pyboot has discovered all the classes"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def before_register(self,context:PyBootContext)->None:
|
|
23
|
+
"""Runs before the pyboot begins the register process of plugins"""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def on_register(self,context:PyBootContext)->None:
|
|
27
|
+
"""Runs when the pyboot has discovered the classes and is registering them via plugin system"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def after_register(self,context:PyBootContext)->None:
|
|
32
|
+
"""Runs after the register process of the plugins and before the load process"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def before_load(self,context:PyBootContext)->None:
|
|
37
|
+
"""Runs before the pyboot load of the plugins"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def on_load(self,context:PyBootContext)->None:
|
|
42
|
+
"""Runs after the pyboot has created the beans indicated in the ioc definition system"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def after_load(self,context:PyBootContext)->None:
|
|
47
|
+
"""Runs after the on_load process has finished on all the plugins"""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def on_ready(self,context:PyBootContext)->None:
|
|
51
|
+
"""Runs at the final stage of application ready"""
|
|
52
|
+
pass
|
pyboot/core/types.py
ADDED
pyboot/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyboot-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, extensible Python framework for building applications with dependency injection and a modular plugin architecture.
|
|
5
|
+
Author: Siddharth Karn
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: pyboot,python-framework,dependency-injection,inversion-of-control,ioc,plugin-architecture,starlette
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# PyBoot
|
|
22
|
+
|
|
23
|
+
**A lightweight, extensible Python framework for building applications with Inversion of Control, Dependency Injection, and a modular plugin architecture.**
|
|
24
|
+
|
|
25
|
+
[](https://www.python.org/)
|
|
26
|
+
[](https://pypi.org/project/pyboot-framework/)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
|
|
29
|
+
PyBoot is a Python framework designed around a simple idea: application components should focus on their responsibilities, while the framework handles object creation, dependency resolution, lifecycle management, and application infrastructure.
|
|
30
|
+
|
|
31
|
+
Inspired by the programming model of frameworks like Spring Boot, PyBoot brings a structured IoC container and dependency injection system to Python without forcing every application to depend on a large collection of built-in features.
|
|
32
|
+
|
|
33
|
+
Its architecture is designed to remain lightweight at its core while allowing developers to extend its capabilities through plugins.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Table of Contents
|
|
38
|
+
|
|
39
|
+
- [Why PyBoot?](#why-pyboot)
|
|
40
|
+
- [Core Philosophy](#core-philosophy)
|
|
41
|
+
- [Features](#features)
|
|
42
|
+
- [Installation](#installation)
|
|
43
|
+
- [Getting Started](#getting-started)
|
|
44
|
+
- [Inversion of Control](#inversion-of-control)
|
|
45
|
+
- [Dependency Injection](#dependency-injection)
|
|
46
|
+
- [Component Management](#component-management)
|
|
47
|
+
- [Application Lifecycle](#application-lifecycle)
|
|
48
|
+
- [Plugin Architecture](#plugin-architecture)
|
|
49
|
+
- [Web Integration](#web-integration)
|
|
50
|
+
- [Project Structure](#project-structure)
|
|
51
|
+
- [Design Philosophy](#design-philosophy)
|
|
52
|
+
- [Roadmap](#roadmap)
|
|
53
|
+
- [Contributing](#contributing)
|
|
54
|
+
- [License](#license)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Why PyBoot?
|
|
59
|
+
|
|
60
|
+
Python has several excellent frameworks for building applications. However, when applications grow, managing dependencies, object creation, shared resources, and application initialization can become increasingly difficult.
|
|
61
|
+
|
|
62
|
+
Developers often end up manually constructing objects and passing dependencies throughout the application.
|
|
63
|
+
|
|
64
|
+
Consider a simple dependency chain:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
UserController
|
|
68
|
+
|
|
|
69
|
+
v
|
|
70
|
+
UserService
|
|
71
|
+
|
|
|
72
|
+
v
|
|
73
|
+
UserRepository
|
|
74
|
+
|
|
|
75
|
+
v
|
|
76
|
+
DatabaseClient
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Without a dependency management system, the developer is responsible for constructing every object and ensuring that the correct dependencies are passed to each constructor.
|
|
80
|
+
|
|
81
|
+
As the application grows, this responsibility becomes increasingly repetitive.
|
|
82
|
+
|
|
83
|
+
PyBoot aims to solve this problem through Inversion of Control.
|
|
84
|
+
|
|
85
|
+
Instead of manually managing application objects, developers declare their components and dependencies. PyBoot takes responsibility for discovering components, constructing objects, resolving dependencies, and managing their lifecycle.
|
|
86
|
+
|
|
87
|
+
The goal is not to introduce another collection of abstractions. It is to provide a predictable application architecture with minimal framework overhead.
|
|
88
|
+
|
|
89
|
+
## Core Philosophy
|
|
90
|
+
|
|
91
|
+
PyBoot is built around four principles.
|
|
92
|
+
|
|
93
|
+
### 1. Inversion of Control
|
|
94
|
+
|
|
95
|
+
Application objects should not be responsible for constructing their own dependencies.
|
|
96
|
+
|
|
97
|
+
The framework manages object creation and dependency resolution through a centralized IoC container.
|
|
98
|
+
|
|
99
|
+
### 2. Constructor-based Dependency Injection
|
|
100
|
+
|
|
101
|
+
Dependencies should be explicitly declared through constructors.
|
|
102
|
+
|
|
103
|
+
This makes dependencies visible, improves testability, and avoids unnecessary service locators or hidden global state.
|
|
104
|
+
|
|
105
|
+
### 3. Lightweight Core
|
|
106
|
+
|
|
107
|
+
The core should contain only the fundamental infrastructure required to manage an application.
|
|
108
|
+
|
|
109
|
+
Features such as web routing, database integration, ORM mapping, validation, and scheduling should be provided through optional extensions.
|
|
110
|
+
|
|
111
|
+
This prevents developers from installing unnecessary dependencies when they only need the IoC container.
|
|
112
|
+
|
|
113
|
+
### 4. Extensibility Through Plugins
|
|
114
|
+
|
|
115
|
+
PyBoot is designed to allow developers to extend the framework without modifying its internal implementation.
|
|
116
|
+
|
|
117
|
+
Plugins can participate in application registration, initialization, and lifecycle management.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Features
|
|
122
|
+
|
|
123
|
+
PyBoot focuses on providing a foundation for structured Python applications.
|
|
124
|
+
|
|
125
|
+
| Feature | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| IoC Container | Centralized management of application objects |
|
|
128
|
+
| Dependency Injection | Automatic constructor dependency resolution |
|
|
129
|
+
| Component Discovery | Discover application components |
|
|
130
|
+
| Lifecycle Management | Control object initialization and application startup |
|
|
131
|
+
| Plugin Architecture | Extend framework capabilities independently |
|
|
132
|
+
| Modular Design | Keep optional functionality outside the core |
|
|
133
|
+
| Python Type Hints | Use Python type annotations for dependency resolution |
|
|
134
|
+
| Starlette Integration | Support web application development through web integration |
|
|
135
|
+
|
|
136
|
+
Some capabilities may depend on the availability of their respective extensions.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Installation
|
|
141
|
+
|
|
142
|
+
Install PyBoot using pip:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install pyboot-framework
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
PyBoot requires Python 3.11 or newer.
|
|
149
|
+
|
|
150
|
+
The core package is designed to remain lightweight. Optional integrations should be installed separately when required.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Getting Started
|
|
155
|
+
|
|
156
|
+
The following example illustrates the intended component-oriented programming model.
|
|
157
|
+
|
|
158
|
+
### 1. Define a component
|
|
159
|
+
|
|
160
|
+
A component is an object managed by the PyBoot IoC container.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from pyboot import Component
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@Component
|
|
167
|
+
class UserService:
|
|
168
|
+
|
|
169
|
+
def get_user(self, user_id: int):
|
|
170
|
+
return {
|
|
171
|
+
"id": user_id,
|
|
172
|
+
"name": "Siddharth"
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
By marking a class as a component, it becomes eligible for framework-managed object creation and dependency injection.
|
|
177
|
+
|
|
178
|
+
### 2. Declare dependencies
|
|
179
|
+
|
|
180
|
+
Components can declare their dependencies through constructor parameters.
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from pyboot import Component
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@Component
|
|
187
|
+
class UserRepository:
|
|
188
|
+
|
|
189
|
+
def find_by_id(self, user_id: int):
|
|
190
|
+
return {
|
|
191
|
+
"id": user_id,
|
|
192
|
+
"name": "Siddharth"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@Component
|
|
197
|
+
class UserService:
|
|
198
|
+
|
|
199
|
+
def __init__(self, repository: UserRepository):
|
|
200
|
+
self.repository = repository
|
|
201
|
+
|
|
202
|
+
def get_user(self, user_id: int):
|
|
203
|
+
return self.repository.find_by_id(user_id)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Here, `UserService` does not create `UserRepository` itself.
|
|
207
|
+
|
|
208
|
+
Instead, PyBoot resolves the dependency and supplies the appropriate component instance.
|
|
209
|
+
|
|
210
|
+
This keeps object construction separate from application business logic.
|
|
211
|
+
|
|
212
|
+
### 3. Build application components
|
|
213
|
+
|
|
214
|
+
The framework discovers registered components and manages their dependencies through its IoC container.
|
|
215
|
+
|
|
216
|
+
The exact bootstrap API depends on the application integration being used.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Inversion of Control
|
|
221
|
+
|
|
222
|
+
Inversion of Control is the foundation of PyBoot.
|
|
223
|
+
|
|
224
|
+
Traditionally, application code controls object creation:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
class UserRepository:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class UserService:
|
|
232
|
+
|
|
233
|
+
def __init__(self):
|
|
234
|
+
self.repository = UserRepository()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
This creates a direct dependency between `UserService` and the construction of `UserRepository`.
|
|
238
|
+
|
|
239
|
+
With PyBoot, object creation is delegated to the framework:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
@Component
|
|
243
|
+
class UserRepository:
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@Component
|
|
248
|
+
class UserService:
|
|
249
|
+
|
|
250
|
+
def __init__(self, repository: UserRepository):
|
|
251
|
+
self.repository = repository
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The framework takes responsibility for:
|
|
255
|
+
|
|
256
|
+
1. Discovering registered component classes.
|
|
257
|
+
2. Identifying their constructor dependencies.
|
|
258
|
+
3. Resolving dependencies from the IoC container.
|
|
259
|
+
4. Creating component instances.
|
|
260
|
+
5. Managing the resulting instances.
|
|
261
|
+
|
|
262
|
+
This provides a centralized mechanism for managing application objects.
|
|
263
|
+
|
|
264
|
+
### Dependency Resolution
|
|
265
|
+
|
|
266
|
+
PyBoot uses Python type annotations to identify constructor dependencies.
|
|
267
|
+
|
|
268
|
+
For example:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
@Component
|
|
272
|
+
class DatabaseClient:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@Component
|
|
277
|
+
class UserRepository:
|
|
278
|
+
|
|
279
|
+
def __init__(self, database: DatabaseClient):
|
|
280
|
+
self.database = database
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@Component
|
|
284
|
+
class UserService:
|
|
285
|
+
|
|
286
|
+
def __init__(self, repository: UserRepository):
|
|
287
|
+
self.repository = repository
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The dependency graph is:
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
UserService
|
|
294
|
+
|
|
|
295
|
+
v
|
|
296
|
+
UserRepository
|
|
297
|
+
|
|
|
298
|
+
v
|
|
299
|
+
DatabaseClient
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The IoC container resolves the dependency chain when constructing the application components.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Component Management
|
|
307
|
+
|
|
308
|
+
Components are the fundamental building blocks of a PyBoot application.
|
|
309
|
+
|
|
310
|
+
A component is a class whose instance is managed by the framework.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
@Component
|
|
314
|
+
class EmailService:
|
|
315
|
+
|
|
316
|
+
def send(self, recipient: str, message: str):
|
|
317
|
+
print(f"Sending email to {recipient}")
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Components allow application developers to organize functionality into independent units.
|
|
321
|
+
|
|
322
|
+
A component can depend on other components, allowing PyBoot to construct complex dependency graphs automatically.
|
|
323
|
+
|
|
324
|
+
### Component Responsibilities
|
|
325
|
+
|
|
326
|
+
Components should:
|
|
327
|
+
|
|
328
|
+
- Encapsulate a specific responsibility.
|
|
329
|
+
- Declare dependencies explicitly.
|
|
330
|
+
- Avoid manually constructing framework-managed dependencies.
|
|
331
|
+
- Keep business logic independent of application bootstrap logic.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Application Lifecycle
|
|
336
|
+
|
|
337
|
+
Application initialization is an important responsibility of a framework.
|
|
338
|
+
|
|
339
|
+
PyBoot is designed around a controlled initialization process rather than requiring every extension to independently initialize itself.
|
|
340
|
+
|
|
341
|
+
The conceptual lifecycle consists of:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
Application Bootstrap
|
|
345
|
+
|
|
|
346
|
+
v
|
|
347
|
+
Plugin Registration
|
|
348
|
+
|
|
|
349
|
+
v
|
|
350
|
+
Component Discovery
|
|
351
|
+
|
|
|
352
|
+
v
|
|
353
|
+
Dependency Resolution
|
|
354
|
+
|
|
|
355
|
+
v
|
|
356
|
+
Bean Creation
|
|
357
|
+
|
|
|
358
|
+
v
|
|
359
|
+
Plugin Initialization
|
|
360
|
+
|
|
|
361
|
+
v
|
|
362
|
+
Application Ready
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
This separation allows plugins to register their capabilities before application initialization and perform setup after the required application objects are available.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Plugin Architecture
|
|
370
|
+
|
|
371
|
+
One of PyBoot's primary design goals is extensibility.
|
|
372
|
+
|
|
373
|
+
Rather than putting every framework feature into the core package, PyBoot provides a plugin architecture through which optional capabilities can be introduced.
|
|
374
|
+
|
|
375
|
+
For example:
|
|
376
|
+
|
|
377
|
+
| Plugin | Responsibility |
|
|
378
|
+
|---|---|
|
|
379
|
+
| Web | HTTP routing, controllers, middleware |
|
|
380
|
+
| ORM | Object-relational mapping and entity management |
|
|
381
|
+
| Database | Database connectivity and repositories |
|
|
382
|
+
| Validation | Request and data validation |
|
|
383
|
+
| Scheduling | Scheduled task execution |
|
|
384
|
+
| Configuration | External configuration management |
|
|
385
|
+
|
|
386
|
+
These are examples of potential extensions, not a claim that every plugin is currently available.
|
|
387
|
+
|
|
388
|
+
### Why Plugins?
|
|
389
|
+
|
|
390
|
+
Consider an application that only needs dependency injection.
|
|
391
|
+
|
|
392
|
+
It should not need to install an ORM, web server, validation engine, or scheduling system.
|
|
393
|
+
|
|
394
|
+
Similarly, an application that needs HTTP routing should be able to install the web integration without forcing database dependencies into its environment.
|
|
395
|
+
|
|
396
|
+
This keeps the core independent of optional infrastructure.
|
|
397
|
+
|
|
398
|
+
### Developing Plugins
|
|
399
|
+
|
|
400
|
+
PyBoot is intended to provide extension points for plugin developers.
|
|
401
|
+
|
|
402
|
+
A plugin should be able to participate in framework registration and application initialization without requiring modifications to the core.
|
|
403
|
+
|
|
404
|
+
The plugin lifecycle is designed around two important phases:
|
|
405
|
+
|
|
406
|
+
**Registration phase**
|
|
407
|
+
|
|
408
|
+
Used to register metadata, components, integrations, and other framework capabilities.
|
|
409
|
+
|
|
410
|
+
**Loading phase**
|
|
411
|
+
|
|
412
|
+
Used to perform initialization that requires framework-managed objects to exist.
|
|
413
|
+
|
|
414
|
+
This separation helps avoid initialization-order problems and allows plugins to integrate with the application lifecycle.
|
|
415
|
+
|
|
416
|
+
Plugin API documentation will be expanded as the extension interfaces stabilize.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Web Integration
|
|
421
|
+
|
|
422
|
+
PyBoot is designed to support web application development through Starlette-based integration.
|
|
423
|
+
|
|
424
|
+
The intention is to combine Starlette's lightweight ASGI infrastructure with PyBoot's IoC container and component management.
|
|
425
|
+
|
|
426
|
+
A typical web application can be organized into:
|
|
427
|
+
|
|
428
|
+
```text
|
|
429
|
+
Application
|
|
430
|
+
|
|
|
431
|
+
+-- Controllers
|
|
432
|
+
|
|
|
433
|
+
+-- Services
|
|
434
|
+
|
|
|
435
|
+
+-- Repositories
|
|
436
|
+
|
|
|
437
|
+
+-- Infrastructure
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
The web integration is responsible for HTTP-specific functionality, while the core remains focused on application object management.
|
|
441
|
+
|
|
442
|
+
This separation means that web-specific dependencies do not need to become mandatory dependencies of the core package.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Project Structure
|
|
447
|
+
|
|
448
|
+
A typical application using PyBoot could follow this structure:
|
|
449
|
+
|
|
450
|
+
```text
|
|
451
|
+
my-application/
|
|
452
|
+
│
|
|
453
|
+
├── app/
|
|
454
|
+
│ │
|
|
455
|
+
│ ├── controllers/
|
|
456
|
+
│ │ └── user_controller.py
|
|
457
|
+
│ │
|
|
458
|
+
│ ├── services/
|
|
459
|
+
│ │ └── user_service.py
|
|
460
|
+
│ │
|
|
461
|
+
│ ├── repositories/
|
|
462
|
+
│ │ └── user_repository.py
|
|
463
|
+
│ │
|
|
464
|
+
│ ├── infrastructure/
|
|
465
|
+
│ │ └── database.py
|
|
466
|
+
│ │
|
|
467
|
+
│ └── main.py
|
|
468
|
+
│
|
|
469
|
+
├── tests/
|
|
470
|
+
│
|
|
471
|
+
├── requirements.txt
|
|
472
|
+
└── README.md
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
This structure is only a recommendation. PyBoot aims to avoid enforcing a particular application directory structure.
|
|
476
|
+
|
|
477
|
+
Developers should be able to organize their applications according to their requirements.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Design Philosophy
|
|
482
|
+
|
|
483
|
+
PyBoot is not intended to replace every Python framework.
|
|
484
|
+
|
|
485
|
+
Its purpose is to provide a structured foundation for building applications with centralized object management and extensible infrastructure.
|
|
486
|
+
|
|
487
|
+
A few important design decisions guide its development:
|
|
488
|
+
|
|
489
|
+
**Explicit dependencies over hidden dependencies**
|
|
490
|
+
|
|
491
|
+
Constructor injection makes the dependency graph visible and easier to understand.
|
|
492
|
+
|
|
493
|
+
**Composition over inheritance**
|
|
494
|
+
|
|
495
|
+
Application functionality should be assembled through components and plugins rather than requiring developers to inherit from large framework classes.
|
|
496
|
+
|
|
497
|
+
**Optional integrations over mandatory dependencies**
|
|
498
|
+
|
|
499
|
+
The core should not require developers to install functionality they do not use.
|
|
500
|
+
|
|
501
|
+
**Centralized lifecycle management**
|
|
502
|
+
|
|
503
|
+
Application initialization should follow a predictable lifecycle instead of depending on arbitrary plugin initialization order.
|
|
504
|
+
|
|
505
|
+
**Extensibility without unnecessary complexity**
|
|
506
|
+
|
|
507
|
+
Plugins should extend the framework through well-defined interfaces rather than modifying its internal implementation.
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Roadmap
|
|
512
|
+
|
|
513
|
+
PyBoot is currently under active development.
|
|
514
|
+
|
|
515
|
+
The following areas represent its development direction:
|
|
516
|
+
|
|
517
|
+
- [x] Initial IoC container architecture
|
|
518
|
+
- [x] Constructor-based dependency injection
|
|
519
|
+
- [x] Component metadata and discovery
|
|
520
|
+
- [ ] Stable application lifecycle API
|
|
521
|
+
- [ ] Stable plugin development API
|
|
522
|
+
- [ ] Configuration management
|
|
523
|
+
- [ ] Bean scopes and lifecycle controls
|
|
524
|
+
- [ ] Web integration
|
|
525
|
+
- [ ] Controller and routing support
|
|
526
|
+
- [ ] Middleware integration
|
|
527
|
+
- [ ] Global exception handling
|
|
528
|
+
- [ ] ORM integration
|
|
529
|
+
- [ ] Repository abstraction
|
|
530
|
+
- [ ] Validation integration
|
|
531
|
+
- [ ] Comprehensive documentation
|
|
532
|
+
- [ ] Testing and performance benchmarks
|
|
533
|
+
|
|
534
|
+
Roadmap items should be updated as features are implemented and released.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## Contributing
|
|
539
|
+
|
|
540
|
+
Contributions, suggestions, bug reports, and discussions are welcome.
|
|
541
|
+
|
|
542
|
+
If you want to contribute:
|
|
543
|
+
|
|
544
|
+
1. Fork the repository.
|
|
545
|
+
2. Clone your fork.
|
|
546
|
+
3. Create a feature branch.
|
|
547
|
+
4. Implement your changes.
|
|
548
|
+
5. Add or update tests.
|
|
549
|
+
6. Submit a pull request.
|
|
550
|
+
|
|
551
|
+
For substantial architectural changes, open an issue first to discuss the proposed design.
|
|
552
|
+
|
|
553
|
+
Please keep contributions consistent with PyBoot's core philosophy: lightweight infrastructure, clear abstractions, and extensibility without unnecessary complexity.
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## License
|
|
558
|
+
|
|
559
|
+
PyBoot is licensed under the MIT License.
|
|
560
|
+
|
|
561
|
+
See the [LICENSE](LICENSE) file for details.
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## Author
|
|
566
|
+
|
|
567
|
+
Developed by **Siddharth Karn**.
|
|
568
|
+
|
|
569
|
+
If you are interested in contributing, improving the architecture, or building extensions for PyBoot, contributions are welcome.
|
|
570
|
+
|
|
571
|
+
**PyBoot — A lightweight foundation for structured Python applications.**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
pyboot/__init__.py,sha256=c07AfR5wiAb-_0sXKI8DYDk9tWBJsB4x_QbdfKi9rTQ,222
|
|
2
|
+
pyboot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pyboot/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pyboot/core/application.py,sha256=rCMLNkaAbRlEdLju6smGN_nFAQRIzLkU6KjBtvgGtNk,6913
|
|
5
|
+
pyboot/core/exceptions.py,sha256=868F3DOdUIsJbn0opn-rCSgxgJzuWhScOwaJabBTIb8,1848
|
|
6
|
+
pyboot/core/ioc.py,sha256=nSpJkHVT6deJxLhaQF9JWoS97wVetS6NWsC56hTsptU,4677
|
|
7
|
+
pyboot/core/plugin_loader.py,sha256=oSQsVgRpJsoZOOaHZVaae017Vdio5nWRTMpkj6DWg8A,995
|
|
8
|
+
pyboot/core/pyboot_context.py,sha256=9hkspedL4OTENb6YYhgqgxlylwzS7kyAGM04dgjzZLU,360
|
|
9
|
+
pyboot/core/pyboot_plugin.py,sha256=3WddbMNgEKhM_PvqIKN1CHXGRj2rSzShN2Ae6dfkV9Q,1814
|
|
10
|
+
pyboot/core/types.py,sha256=xUnJk7f-FtmhznnMo8GGvAKhVwejjM9NQOPsInbI0UQ,140
|
|
11
|
+
pyboot/core/builtins/BuiltInAttr.py,sha256=jYuxOOf7Efe9Vm96Y4HZMtUaOMhbgZ7oSCrun4e9foE,244
|
|
12
|
+
pyboot/core/builtins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
pyboot/core/builtins/component.py,sha256=ZkmbKF7r30FbwWY2yaS5SbvAo82n0su-M9xAXh-ZF3s,1518
|
|
14
|
+
pyboot/core/builtins/configuration.py,sha256=BjeRfoz77wGwR0jDExsJPnwA3XhEEWC_xuEYbViCkxM,3752
|
|
15
|
+
pyboot/core/builtins/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
pyboot_framework-0.1.0.dist-info/METADATA,sha256=VPFo-IRJzXUIjkTdRnev9vR9mHl7daBKo8lYOYm3NZg,17078
|
|
17
|
+
pyboot_framework-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
18
|
+
pyboot_framework-0.1.0.dist-info/entry_points.txt,sha256=qX-VV6uSkQe9tmcpFm3idvG9qxqrcMcqKEsMfy36pdM,147
|
|
19
|
+
pyboot_framework-0.1.0.dist-info/top_level.txt,sha256=0RlOji1KqNyWOq0MS6pPeyLcMXlLapCTPSKFGgYH1yw,7
|
|
20
|
+
pyboot_framework-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyboot
|