Skip to content

Commit 3e18ea7

Browse files
committed
Added some documentations
1 parent a19a5b8 commit 3e18ea7

File tree

13 files changed

+231
-104
lines changed

13 files changed

+231
-104
lines changed

docs/cli/new-command.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,57 @@ path/to/scaffold-the-new-project/
5151

5252
## **New Command CLI Arguments**
5353
- `project-name` Set the resulting project module name.
54-
- `directory` Path to dump the scaffolded files. `.` can be used to select current directory.
54+
- `directory` Path to dump the scaffolded files. `.` can be used to select the current directory.
55+
56+
57+
## **New Project without pyproject requirement**
58+
To scaffold a new project without `pyproject.toml`, add `--plain` to the `ellar new command`. For example,
59+
60+
```shell
61+
ellar new my-project --plain
62+
```
63+
64+
This will create a folder as follows:
65+
```angular2html
66+
my-project/
67+
├─ my_project/
68+
│ ├─ apps/
69+
│ │ ├─ __init__.py
70+
│ ├─ core/
71+
│ ├─ config.py
72+
│ ├─ domain
73+
│ ├─ root_module.py
74+
│ ├─ server.py
75+
│ ├─ __init__.py
76+
├─ tests/
77+
│ ├─ __init__.py
78+
├─ manage.py
79+
├─ README.md
80+
```
81+
Inside the scaffolded project, the `manage.py` python file is not the entry point for the application.
82+
It creates a new CLI interface and uses that as the new CLI command to interact with the project.
83+
84+
```shell
85+
python manage.py
86+
87+
### OUTPUT
88+
89+
Usage: manage.py [OPTIONS] COMMAND [ARGS]...
90+
91+
Ellar, ASGI Python Web framework
92+
93+
Options:
94+
--project TEXT Run Specific Command on a specific project [default:
95+
default]
96+
-v, --version Show the version and exit.
97+
--help Show this message and exit.
98+
99+
Commands:
100+
create-module - Scaffolds Ellar Application Module -
101+
runserver - Starts Uvicorn Server -
102+
103+
```
104+
Other ellar commands and will be executed through `manage.py` python file. Eg:
105+
```shell
106+
python manage.py runserver
107+
```

docs/custom-setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Create a file `controller.py`:
1111
```Python
1212
from ellar.common import ModuleRouter, Controller, get
1313

14-
router = ModuleRouter('', tag='Math')
14+
router = ModuleRouter('')
1515

1616

1717
@router.get("/add")

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Ellar was deeply influenced by [NestJS](https://docs.nestjs.com/){target="_blank
2323
Also, Ellar took some concepts from [FastAPI](https://fastapi.tiangolo.com/){target="_blank"} in terms of request parameter handling and data serialization with Pydantic.
2424

2525
The objective of Ellar is to provide a high level of abstracted interface to your python web app, along with a well-structured project setup, give room for object-oriented approach to web application design,
26-
allow you chose your desired application architecture, and ultimately, deliver speedy handling to requests using any ASGI server.
26+
allow you to choose your desired application architecture, and ultimately, deliver speedy handling to requests using any ASGI server.
2727

2828
## **Project Status**
2929
Beta version

docs/overview/controllers.md

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# **Controllers**
2-
The Controller is responsible for handling incoming requests and returning responses to the client.
3-
The purpose of a controller is to receive specific requests for an application `ApplicationRouter`. `ApplicationRouter` on the other hand, decides which `controller` should handle an incoming request.
2+
The Controller plays a crucial role in managing incoming requests and providing responses to clients.
3+
Its primary function is to handle specific requests directed to an application's `ApplicationRouter`.
4+
In turn, the `ApplicationRouter` determines the appropriate `controller` to manage the incoming request.
45

56
![controller description image](../img/controller_description.png)
67

7-
Controllers can be said to be a router with many routes registered in them.
8+
Conceptually, controllers can be likened to routers with multiple registered routes within them.
89

910
### **Creating a Controller**
1011
To create a controller, we use classes and decorators. The `Controller` decorator associates classes with a required
@@ -19,13 +20,13 @@ class UserController(ControllerBase):
1920
```
2021

2122
## **Routing**
22-
In this section, we are going to highlight key features of the `@Controller()`, a `class decorator`
23-
for defining a controller. By default, `@Controller()` will create a path prefix `/car` gotten from the class name in `Car`Controller.
24-
This will be used to group related routes and minimize duplicate route definitions.
25-
26-
For example, we may choose to group a set of routes that manage interactions with a customer entity under the route `/user`.
27-
In that case, we could specify the path prefix `/user` in the `@Controller()` decorator so we don't have to repeat that portion of the path for each route in the controller.
23+
In this section, we will outline the key features of `@Controller()`, a `class decorator` designed for defining a controller.
24+
By default, when applied, `@Controller()` generates a path prefix based on the class name, such as `/car`
25+
for a controller named `CarController`. This feature aims to organize and group related routes, reducing redundancy in route definitions.
2826

27+
For instance, if we want to group a collection of routes managing interactions with a customer entity under the route `/user`,
28+
we can specify the path prefix `/user` in the `@Controller()` decorator.
29+
This ensures that we don't need to repeat this portion of the path for each route within the controller.
2930
```python
3031
# project_name/apps/car/controllers.py
3132

@@ -43,17 +44,20 @@ class CarController(ControllerBase):
4344
```
4445

4546
!!! hint
46-
Class Decorators name are capitalized while function/method decorator name are in lower case
47+
Class decorators are conventionally named with capital letters, while function/method decorator names typically use lowercase letters.
48+
4749

48-
The `@get()` HTTP method decorator before the `get_all(self)` method marks `get_all(self)` as the HTTP request handler that will handle a specific endpoint matching the route path and HTTP method of `GET`.
50+
The `@get()` HTTP method decorator preceding the `get_all(self)` method designates `get_all(self)` as the HTTP request
51+
handler responsible for handling a specific endpoint matching the route path and HTTP method of `GET`.
4952

50-
But what then is the route path of `get_all(self)`? The route path is determined by concatenating the controller `path prefix` and the path specified in the HTTP method function decorator `@get()`.
53+
But what exactly is the route path for `get_all(self)`? The route path is determined by combining the controller's
54+
`path prefix` and the path specified in the HTTP method function decorator `@get()`.
5155

52-
For example, we've declared a prefix for every route `(car)`, and haven't added any path information in the decorator, which means the path will default to `/`. In that case,
53-
Ellar will map `GET /car/` requests to the `get_all(self)` handler.
56+
For instance, if we've set a prefix for every route `(car)` and haven't added any path information in the
57+
decorator, it means the path defaults to `/`. In this case, Ellar will associate `GET /car/` requests with the `get_all(self)` handler.
5458

55-
Another example to help make things clear, a path prefix of `/users` combined with the decorator `@get('/profile')` would produce a route mapping for requests like
56-
`GET /users/profile`.
59+
To illustrate further, if we have a path prefix of `/users` and include the decorator `@get('/profile')`,
60+
it would result in a route mapping for requests like `GET /users/profile`.
5761

5862

5963
### **Overview of HTTP function decorator parameters:**
@@ -248,10 +252,11 @@ async def create(self, payload: Body[CreateCarSerializer]):
248252
return 'This action adds a new car'
249253
```
250254

251-
`CreateCarSerializer` is a pydantic type. These means `name`, `year` and `model` fields are type validated out of the box.
255+
`CreateCarSerializer` is a Pydantic type, which implies that the `name`, `year`, and `model` fields undergo automatic type validation.
252256

253-
It's important to note the way we used `CreateCarSerializer` as a type annotation of the `payload` parameter in the `create` route handler method.
254-
Ellar will compute values for all the route handler parameters and validates them based on the annotated types before executing the handler. 
257+
It's crucial to observe how we utilized `CreateCarSerializer` as a type annotation for the `payload` parameter in the `create`
258+
route handler method. Ellar will calculate values for all route handler parameters and validate them according to
259+
the annotated types before executing the handler.
255260

256261
!!! info
257262
if a parameter is not annotated, it will be assumed as a `string` type
@@ -298,11 +303,11 @@ class CarController(ControllerBase):
298303
```
299304
## **Linking Controller**
300305

301-
In the previous page, we already wired our `car` module (`CarModule`) to `ApplicationModule` in `project_name/root_module`
302-
but this time we shall be adding things to the `CarModule`. To keep things more simple, organized, and modular.
306+
On the preceding page, we successfully connected our `car` module (`CarModule`) to the `ApplicationModule` in `project_name/root_module`.
307+
However, this time, we will be introducing modifications to the `CarModule` to keep things simple, organized and modular.
303308

304-
Let's register `CarController` to `CarModule`.
305-
`@Module()` takes `controllers` as a parameter which is an array of the `ControllerBase` type.
309+
To achieve this, let's include the registration of `CarController` within the `CarModule`.
310+
The `@Module()` decorator accepts a `controllers` parameter, which is an array of the `ControllerBase` type.
306311

307312
In the `car/module.py`,
308313

docs/overview/module-router.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# **Module Router**
22

3-
ModuleRouter allows you to define your route handlers as standalone functions, providing an alternative to using classes.
4-
This can be beneficial for python developers who prefer using functions.
5-
It is important to note that using ModuleRouter does not limit your access to other features provided by Ellar.
3+
`ModuleRouter` allows you to define your route handlers as standalone functions, offering an alternative to using classes.
4+
This can be advantageous for Python developers who prefer using functions.
5+
Importantly, using `ModuleRouter` does not restrict your access to other features provided by Ellar.
66

77
## **Usage**
8-
The Ellar CLI tool generates a `routers.py` file in every `create-module` scaffold command.
9-
This file contains a quick guide on how to use the `ModuleRouter` class.
10-
11-
Let's use the **routers.py** created in our previous project. And create **two** route functions, **addition** and **subtraction**
8+
The Ellar CLI tool automatically generates a `routers.py` file with every `create-module` scaffold command.
9+
This file serves as a concise guide on utilizing the `ModuleRouter` class.
1210

11+
Now, let's leverage the **routers.py** file generated in our prior project to implement **two** route functions, namely **addition** and **subtraction**.
1312
```python
1413
# project_name/apps/car/routers.py
1514
"""
@@ -23,25 +22,30 @@ def index(request: Request):
2322
return {'detail': 'Welcome to Cats Resource'}
2423
"""
2524
from ellar.common import ModuleRouter
25+
from ellar.openapi import ApiTags
2626

27-
math_router = ModuleRouter('/math', tag='Math')
27+
math_router = ModuleRouter('/math')
28+
open_api_tag = ApiTags(name='Math')
29+
open_api_tag(math_router.get_control_type())
2830

2931
@math_router.get('/add')
30-
def addition(a:int, b:int):
32+
def addition(a: int, b: int):
3133
return a + b
3234

3335

3436
@math_router.get('/subtract')
35-
def subtraction(a:int, b:int):
37+
def subtraction(a: int, b: int):
3638
return a - b
39+
3740
```
38-
In the example above, we created `math_router` with a prefix `/math` and a OPENAPI tag 'math'. Then we added two routes `addition(a:int, b:int)` and `subtraction(a:int, b:int)`.
39-
Each route takes two query parameters, 'a' and 'b' which are declared as int type. These functions handle the query parameters and return the result of the mathematical operation.
4041

41-
Next, we have to make the `math_router` visible to the application
42+
In the provided example, the `math_router` is created with a prefix `/math` and an OPENAPI tag 'Math'.
43+
Two routes, `addition(a:int, b:int)` and `subtraction(a:int, b:int)`, are added to the router, each handling two query parameters ('a' and 'b') of integer type. These functions perform the specified mathematical operations and return the results.
44+
45+
To make the `math_router` visible to the application, it is registered with the current injector using `current_injector.register(ModuleRouter, math_router)`. This step ensures that the router is recognized and accessible within the application.
4246

4347
## **Registering Module Router**
44-
Like controllers, ModuleRouters also need to be registered to their root module in order to be used in a web application.
48+
Like controllers, ModuleRouters also need to be registered to their root module to be used in a web application.
4549
In the example provided above, the `math_router` would be registered under the `project_name/apps/car/module.py` file.
4650

4751
This registration process typically involves importing the `math_router` and then adding it to the list of `routers` in the `module.py` file.

docs/overview/modules.md

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
# **Modules**
22

3-
A module is a class annotated with a `@Module()` decorator.
4-
The `@Module()` decorator provides **metadata** that exports a `Module` data and defines the module structure.
3+
A module is represented by a class with the `@Module()` decorator.
4+
This decorator provides essential metadata, offering a `Module` data structure that outlines the module's architecture.
55

66
![middleware description image](../img/ModuleDescription.png)
77

8-
The best way to organize your components is to build your projects as `Modules`.
8+
Organizing components within projects as `Modules` is considered a best practice.
9+
The `ApplicationModule` serves as the starting point or root module for constructing the application module tree.
10+
This internal data structure facilitates the resolution of relationships, dependencies, and interactions between modules and providers.
911

10-
The `ApplicationModule` is the entry-point/root module to building the application module tree -
11-
the internal data structure used to resolve `module` and `provider` relationships and dependencies.
12-
13-
Thus, the architecture resulting from most applications will include multiple modules with closely related **functionality**.
12+
Consequently, the typical architecture of applications involves multiple modules, each encapsulating closely related functionality.
1413

1514
## **Feature modules**
16-
Building an application as a group of feature modules bundled together helps to manage complexity, have a maintainable, extendable, and testable code base, and encourage development using SOLID principles.
17-
18-
A typical example of a feature module is the **car** project. The `CarModule` wraps all the services and controller that manages the `car` resource which makes it easy to maintain, extend, and testable.
19-
```python title='project_name/apps/car/module.py' linenums="1"
15+
Building an application as a group of feature modules bundled together helps manage complexity,
16+
maintain a codebase that is both extendable and testable, and encourages development using SOLID principles.
2017

18+
A typical example of a feature module is the **car** project.
19+
The `CarModule` wraps all the services and controllers responsible for managing the `car` resource,
20+
making it easy to maintain, extend, and test.
2121

22+
```python title='project_name/apps/car/module.py' linenums="1"
2223
from ellar.common import Module
2324
from ellar.core import ModuleBase
2425
from ellar.di import Container
@@ -76,22 +77,67 @@ class BookModule(ModuleBase):
7677
## **Additional Module Configurations**
7778

7879
### **Module Events**
79-
Every registered Module receives two event calls during its instantiation and when the application is ready.
80+
Before a Module is instantiated, the `before_init` class method is invoked with an application config object.
81+
This allows for the configuration of additional initialization parameters that need to be supplied to the `__init__`
82+
function, originating from the application config.
8083

8184
```python linenums="1"
85+
import typing
8286
from ellar.common import Module
8387
from ellar.core import ModuleBase, Config
8488

89+
8590
@Module()
8691
class ModuleEventSample(ModuleBase):
8792
@classmethod
88-
def before_init(cls, config: Config) -> None:
93+
def before_init(cls, config: Config) -> typing.Any:
8994
"""Called before creating Module object"""
9095

9196
```
9297
`before_init` receives current app `Config` as a parameter for further configurations before `ModuleEventSample` is initiated.
9398
It's important to note that returned values from `before_init` will be passed to the constructor of `ModuleEventSample` during instantiation.
9499

100+
### **Module Application Cycle**
101+
The Ellar application follows a two-phase lifecycle, consisting of `on_startup` and `on_shutdown`, which are managed by `EllarApplicationLifespan`.
102+
103+
- `on_startup`: Triggered when the ASGI server initiates a lifespan request on the application instance.
104+
- `on_shutdown`: Activated when the ASGI server is in the process of shutting down the application.
105+
106+
Modules can subscribe to these events by inheriting from `IApplicationStartup` for startup actions and `IApplicationShutdown` for shutdown actions.
107+
108+
For instance:
109+
```python
110+
from ellar.common import Module, IApplicationShutdown, IApplicationStartup
111+
from ellar.core import ModuleBase
112+
113+
@Module()
114+
class AModuleSample(ModuleBase, IApplicationStartup):
115+
async def on_startup(self, app: "App") -> None:
116+
pass
117+
118+
@Module()
119+
class BModuleSample(ModuleBase, IApplicationShutdown):
120+
async def on_shutdown(self) -> None:
121+
pass
122+
123+
@Module()
124+
class CModuleSample(ModuleBase, IApplicationStartup, IApplicationShutdown):
125+
async def on_startup(self, app: "App") -> None:
126+
pass
127+
128+
async def on_shutdown(self) -> None:
129+
pass
130+
```
131+
132+
In the provided example:
133+
- `AModuleSample` subscribes to `IApplicationStartup` and will be called only during startup.
134+
- `BModuleSample` subscribes to `IApplicationShutdown` and will be called only during application shutdown.
135+
- `CModuleSample` subscribes to both `IApplicationStartup` and `IApplicationShutdown`, thus being invoked during both startup and shutdown phases.
136+
137+
This modular approach enables modules to actively engage in the application's life-cycle events, allowing developers
138+
to organize and execute code efficiently during specific stages.
139+
This not only enhances flexibility but also contributes to the overall maintainability of the application.
140+
95141
### **Module Exceptions**
96142
Custom exception handlers can be registered through modules.
97143

@@ -108,12 +154,11 @@ class ModuleExceptionSample(ModuleBase):
108154
`exception_404_handler` will be register to the application at runtime during `ModuleExceptionSample` computation.
109155

110156
### **Module Templating Filters**
111-
We can also define `Jinja2` templating filters in project Modules or any `@Module()` module.
112-
The defined filters are be passed down to `Jinja2` **environment** instance alongside the `template_folder`
113-
value when creating **TemplateLoader**.
114-
115-
```python linenums="1"
157+
In addition, we can define `Jinja2` templating filters within project Modules or any module annotated with `@Module()`.
158+
The specified filters are then passed down to the `Jinja2` **environment** instance alongside the `template_folder`
159+
value when creating the **TemplateLoader**.
116160

161+
```python
117162
from ellar.common import Module, template_global, template_filter
118163
from ellar.core import ModuleBase
119164

@@ -132,6 +177,7 @@ class ModuleTemplateFilterSample(ModuleBase):
132177
return n * 2
133178
```
134179

180+
135181
## **Dependency Injection**
136182
A module class can inject providers as well (e.g., for configuration purposes):
137183

0 commit comments

Comments
 (0)