Skip to content

Commit 83cbd84

Browse files
authored
Merge pull request #75 from eadwinCode/dynamic_setup_fix
Dynamic Setup Fix
2 parents 3d6653f + e10d78e commit 83cbd84

File tree

6 files changed

+51
-24
lines changed

6 files changed

+51
-24
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@ Ellar is based on [Starlette (ASGI toolkit)](https://www.starlette.io/), a light
1919
While Ellar provides a high level of abstraction on top of Starlette, it still incorporates some of its features, as well as those of FastAPI.
2020
If you are familiar with these frameworks, you will find it easy to understand and use Ellar.
2121

22+
## Features Summary
23+
24+
- **Easy to Use**: Ellar has a simple and intuitive API that makes it easy to get started with building a fast and scalable web applications or web APIs in Python.
25+
- **Dependency Injection (DI)**: It comes with DI system makes it easy to manage dependencies and reduce coupling between components.
26+
- **Pydantic Integration**: It is properly integrated with Pydantic, a popular Python library for data validation, to ensure that input data is valid.
27+
- **Templating with Jinja2**: Ellar provides built-in support for Jinja2 templates, making it easy to create dynamic web pages.
28+
- **OpenAPI Documentation**: It comes with built-in support for OpenAPI documentation, making it easy to generate `Swagger` or `ReDoc` documentation for your API. And more can be added with ease if necessary.
29+
- **Controller (MVC) Architecture**: Ellar's controller architecture follows the Model-View-Controller (MVC) pattern, making it easy to organize your code.
30+
- **Guards for Authentication and Authorization**: It provides built-in support for guards, allowing you to easily implement authentication and authorization in your application.
31+
- **Modularity**: Ellar follows a modular architecture inspired by NestJS, making it easy to organize your code into reusable modules.
32+
- **Asynchronous programming**: It allows you to takes advantage of Python's `async/await` feature to write efficient and fast code that can handle large numbers of concurrent requests
33+
2234
## Dependencies
2335
- Python >= 3.7
2436
- Starlette
2537
- Injector
2638
- Pydantic
2739

28-
## Features Summary
29-
- `Pydantic integration`
30-
- `Dependency Injection (DI)`
31-
- `Templating with Jinja2`
32-
- `OpenAPI Documentation (Swagger and ReDoc)`
33-
- `Controller (MVC)`
34-
- `Guards (authentications, roles and permissions)`
35-
- `Modularization (eg: flask blueprint)`
36-
- `Websocket support`
37-
- `Session and Cookie support`
38-
- `CORS, GZip, Static Files, Streaming responses`
39-
4040
## Installation
4141
### Poetry Installation
4242
For [Poetry](https://python-poetry.org/) usages

docs/index.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ Additionally, it took some concepts from [FastAPI](https://fastapi.tiangolo.com/
2626
With that said, the objective of Ellar is to offer a high level of abstraction in its framework APIs, along with a well-structured project setup, an object-oriented approach to web application design,
2727
the ability to adapt to any desired software architecture, and ultimately, speedy request handling.
2828

29+
30+
## Features Summary
31+
32+
- **Easy to Use**: Ellar has a simple and intuitive API that makes it easy to get started with building a fast and scalable web applications or web APIs in Python.
33+
- **Dependency Injection (DI)**: It comes with DI system makes it easy to manage dependencies and reduce coupling between components.
34+
- **Pydantic Integration**: It is properly integrated with Pydantic, a popular Python library for data validation, to ensure that input data is valid.
35+
- **Templating with Jinja2**: Ellar provides built-in support for Jinja2 templates, making it easy to create dynamic web pages.
36+
- **OpenAPI Documentation**: It comes with built-in support for OpenAPI documentation, making it easy to generate `Swagger` or `ReDoc` documentation for your API. And more can be added with ease if necessary.
37+
- **Controller (MVC) Architecture**: Ellar's controller architecture follows the Model-View-Controller (MVC) pattern, making it easy to organize your code.
38+
- **Guards for Authentication and Authorization**: It provides built-in support for guards, allowing you to easily implement authentication and authorization in your application.
39+
- **Modularity**: Ellar follows a modular architecture inspired by NestJS, making it easy to organize your code into reusable modules.
40+
- **Asynchronous programming**: It allows you to takes advantage of Python's `async/await` feature to write efficient and fast code that can handle large numbers of concurrent requests
41+
2942
## Installation
3043
To get started, you need to scaffold a project using [Ellar-CLI](https://eadwincode.github.io/ellar-cli/) toolkit. This is recommended for a first-time user.
3144
The scaffolded project is more like a guide to project setup.
@@ -48,18 +61,6 @@ $(venv) ellar runserver --reload
4861
Open your browser and navigate to [`http://localhost:8000/`](http://localhost:8000/).
4962
![Swagger UI](img/ellar_framework.png)
5063

51-
## Features Summary
52-
- `Pydantic integration`
53-
- `Dependency Injection (DI)`
54-
- `Templating with Jinja2`
55-
- `OpenAPI Documentation (Swagger and ReDoc)`
56-
- `Controller (MVC)`
57-
- `Guards (authentications, roles and permissions)`
58-
- `Modularization (eg: flask blueprint)`
59-
- `Websocket support`
60-
- `Session and Cookie support`
61-
- `CORS, GZip, Static Files, Streaming responses`
62-
6364
## Dependency Summary
6465
- `Python >= 3.7`
6566
- `Starlette`

ellar/core/factory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def read_all_module(cls, module_config: ModuleSetup) -> t.Dict[t.Type, ModuleSet
5050
module_dependency = OrderedDict()
5151
for module in modules:
5252
if isinstance(module, DynamicModule):
53+
module.apply_configuration()
5354
module_config = ModuleSetup(module.module)
5455
elif isinstance(module, ModuleSetup):
5556
module_config = module

ellar/core/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def install_module(
138138
**init_kwargs: t.Any,
139139
) -> t.Union[T, ModuleBase]:
140140
if isinstance(module, DynamicModule):
141+
module.apply_configuration()
141142
module_config = ModuleSetup(module.module, init_kwargs=init_kwargs)
142143
else:
143144
module_config = ModuleSetup(module, init_kwargs=init_kwargs)

ellar/core/modules/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,16 @@ class DynamicModule:
3737
commands: t.Sequence[t.Union[t.Callable, "EllarTyper"]] = dataclasses.field(
3838
default_factory=lambda: tuple()
3939
)
40+
_is_configured: bool = False
4041

4142
def __post_init__(self) -> None:
4243
if not isinstance(self.module, type) or not issubclass(self.module, ModuleBase):
4344
raise Exception(f"{self.module.__name__} is not a valid Module")
4445

46+
def apply_configuration(self) -> None:
47+
if self._is_configured:
48+
return
49+
4550
kwargs = dict(
4651
controllers=list(self.controllers),
4752
routers=list(self.routers),
@@ -59,6 +64,8 @@ def __post_init__(self) -> None:
5964
reflect.delete_metadata(key, self.module)
6065
reflect.define_metadata(key, value, self.module)
6166

67+
self._is_configured = True
68+
6269

6370
@dataclasses.dataclass
6471
class ModuleSetup:
@@ -143,6 +150,7 @@ def configure_with_factory(
143150
f"Factory function for {self.module.__name__} module "
144151
f"configuration must return `DynamicModule` instance"
145152
)
153+
res.apply_configuration()
146154

147155
init_kwargs = dict(self.init_kwargs)
148156
return create_module_ref_factor(self.module, config, container, **init_kwargs)

tests/test_modules/test_module_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from abc import ABC
2+
from unittest.mock import patch
23

34
import pytest
45

@@ -188,3 +189,18 @@ def test_invalid_dynamic_module_setup():
188189
with pytest.raises(Exception) as ex:
189190
DynamicModule(module=IDynamic)
190191
assert str(ex.value) == "IDynamic is not a valid Module"
192+
193+
194+
def test_can_not_apply_dynamic_module_twice():
195+
dynamic_type = type("DynamicSample", (IDynamic,), {"a": "1222", "b": "121212"})
196+
with patch.object(reflect.__class__, "define_metadata") as mock_define_metadata:
197+
dynamic_module = DynamicModule(
198+
module=DynamicInstantiatedModule,
199+
providers=[ProviderConfig(IDynamic, use_class=dynamic_type)],
200+
)
201+
dynamic_module.apply_configuration()
202+
assert mock_define_metadata.called
203+
204+
with patch.object(reflect.__class__, "define_metadata") as mock_define_metadata:
205+
dynamic_module.apply_configuration()
206+
assert mock_define_metadata.called is False

0 commit comments

Comments
 (0)