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 ADDED
@@ -0,0 +1,6 @@
1
+ from pyboot.core.application import *
2
+ from pyboot.core.types import *
3
+ from pyboot.core.pyboot_plugin import *
4
+ from pyboot.core.pyboot_context import *
5
+ from pyboot.core.ioc import *
6
+ from pyboot.core.exceptions import *
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
+
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+ class BuiltInAttr(Enum):
4
+ IS_PYBOOT_COMPONENT_CLASS="__is_pyboot_component_class__"
5
+
6
+ IS_PYBOOT_CONFIGURATION_CLASS="__is_pyboot_configuration_class__"
7
+
8
+ IS_PYBOOT_BEAN_FACTORY="__is_pyboot_bean_factory__"
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
@@ -0,0 +1,7 @@
1
+ from typing import TypeVar,Callable
2
+
3
+ #Class Type
4
+ ClassTBase=type
5
+ ClassT=TypeVar("ClassT",bound=type)
6
+
7
+ MethodTBase=Callable[...,object]
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
+ [![Python](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/)
26
+ [![PyPI](https://img.shields.io/pypi/v/pyboot-framework)](https://pypi.org/project/pyboot-framework/)
27
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [pyboot.plugins]
2
+ component = pyboot.core.builtins.component.ComponentPlugin
3
+ configuration = pyboot.core.builtins.configuration.ConfigurationPlugin
@@ -0,0 +1 @@
1
+ pyboot