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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@ Ellar is based on [Starlette (ASGI toolkit)](https://www.starlette.io/), a light
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.
If you are familiar with these frameworks, you will find it easy to understand and use Ellar.

## Features Summary

- **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.
- **Dependency Injection (DI)**: It comes with DI system makes it easy to manage dependencies and reduce coupling between components.
- **Pydantic Integration**: It is properly integrated with Pydantic, a popular Python library for data validation, to ensure that input data is valid.
- **Templating with Jinja2**: Ellar provides built-in support for Jinja2 templates, making it easy to create dynamic web pages.
- **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.
- **Controller (MVC) Architecture**: Ellar's controller architecture follows the Model-View-Controller (MVC) pattern, making it easy to organize your code.
- **Guards for Authentication and Authorization**: It provides built-in support for guards, allowing you to easily implement authentication and authorization in your application.
- **Modularity**: Ellar follows a modular architecture inspired by NestJS, making it easy to organize your code into reusable modules.
- **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

## Dependencies
- Python >= 3.7
- Starlette
- Injector
- Pydantic

## Features Summary
- `Pydantic integration`
- `Dependency Injection (DI)`
- `Templating with Jinja2`
- `OpenAPI Documentation (Swagger and ReDoc)`
- `Controller (MVC)`
- `Guards (authentications, roles and permissions)`
- `Modularization (eg: flask blueprint)`
- `Websocket support`
- `Session and Cookie support`
- `CORS, GZip, Static Files, Streaming responses`

## Installation
### Poetry Installation
For [Poetry](https://python-poetry.org/) usages
Expand Down
25 changes: 13 additions & 12 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ Additionally, it took some concepts from [FastAPI](https://fastapi.tiangolo.com/
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,
the ability to adapt to any desired software architecture, and ultimately, speedy request handling.


## Features Summary

- **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.
- **Dependency Injection (DI)**: It comes with DI system makes it easy to manage dependencies and reduce coupling between components.
- **Pydantic Integration**: It is properly integrated with Pydantic, a popular Python library for data validation, to ensure that input data is valid.
- **Templating with Jinja2**: Ellar provides built-in support for Jinja2 templates, making it easy to create dynamic web pages.
- **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.
- **Controller (MVC) Architecture**: Ellar's controller architecture follows the Model-View-Controller (MVC) pattern, making it easy to organize your code.
- **Guards for Authentication and Authorization**: It provides built-in support for guards, allowing you to easily implement authentication and authorization in your application.
- **Modularity**: Ellar follows a modular architecture inspired by NestJS, making it easy to organize your code into reusable modules.
- **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

## Installation
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.
The scaffolded project is more like a guide to project setup.
Expand All @@ -48,18 +61,6 @@ $(venv) ellar runserver --reload
Open your browser and navigate to [`http://localhost:8000/`](http://localhost:8000/).
![Swagger UI](img/ellar_framework.png)

## Features Summary
- `Pydantic integration`
- `Dependency Injection (DI)`
- `Templating with Jinja2`
- `OpenAPI Documentation (Swagger and ReDoc)`
- `Controller (MVC)`
- `Guards (authentications, roles and permissions)`
- `Modularization (eg: flask blueprint)`
- `Websocket support`
- `Session and Cookie support`
- `CORS, GZip, Static Files, Streaming responses`

## Dependency Summary
- `Python >= 3.7`
- `Starlette`
Expand Down
1 change: 1 addition & 0 deletions ellar/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def read_all_module(cls, module_config: ModuleSetup) -> t.Dict[t.Type, ModuleSet
module_dependency = OrderedDict()
for module in modules:
if isinstance(module, DynamicModule):
module.apply_configuration()
module_config = ModuleSetup(module.module)
elif isinstance(module, ModuleSetup):
module_config = module
Expand Down
1 change: 1 addition & 0 deletions ellar/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def install_module(
**init_kwargs: t.Any,
) -> t.Union[T, ModuleBase]:
if isinstance(module, DynamicModule):
module.apply_configuration()
module_config = ModuleSetup(module.module, init_kwargs=init_kwargs)
else:
module_config = ModuleSetup(module, init_kwargs=init_kwargs)
Expand Down
8 changes: 8 additions & 0 deletions ellar/core/modules/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ class DynamicModule:
commands: t.Sequence[t.Union[t.Callable, "EllarTyper"]] = dataclasses.field(
default_factory=lambda: tuple()
)
_is_configured: bool = False

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

def apply_configuration(self) -> None:
if self._is_configured:
return

kwargs = dict(
controllers=list(self.controllers),
routers=list(self.routers),
Expand All @@ -59,6 +64,8 @@ def __post_init__(self) -> None:
reflect.delete_metadata(key, self.module)
reflect.define_metadata(key, value, self.module)

self._is_configured = True


@dataclasses.dataclass
class ModuleSetup:
Expand Down Expand Up @@ -143,6 +150,7 @@ def configure_with_factory(
f"Factory function for {self.module.__name__} module "
f"configuration must return `DynamicModule` instance"
)
res.apply_configuration()

init_kwargs = dict(self.init_kwargs)
return create_module_ref_factor(self.module, config, container, **init_kwargs)
Expand Down
16 changes: 16 additions & 0 deletions tests/test_modules/test_module_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC
from unittest.mock import patch

import pytest

Expand Down Expand Up @@ -188,3 +189,18 @@ def test_invalid_dynamic_module_setup():
with pytest.raises(Exception) as ex:
DynamicModule(module=IDynamic)
assert str(ex.value) == "IDynamic is not a valid Module"


def test_can_not_apply_dynamic_module_twice():
dynamic_type = type("DynamicSample", (IDynamic,), {"a": "1222", "b": "121212"})
with patch.object(reflect.__class__, "define_metadata") as mock_define_metadata:
dynamic_module = DynamicModule(
module=DynamicInstantiatedModule,
providers=[ProviderConfig(IDynamic, use_class=dynamic_type)],
)
dynamic_module.apply_configuration()
assert mock_define_metadata.called

with patch.object(reflect.__class__, "define_metadata") as mock_define_metadata:
dynamic_module.apply_configuration()
assert mock_define_metadata.called is False