Skip to content

Commit ad9996f

Browse files
authored
Merge pull request #54 from eadwinCode/document_updates_jan_2023
Document updates Jan 2023
2 parents 4f8f29a + 2ca1022 commit ad9996f

File tree

8 files changed

+657
-5
lines changed

8 files changed

+657
-5
lines changed

docs/basics/execution-context.md

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This allows different components of the application like **exception handlers**,
77
There are two class `HostContext` and `ExecutionContext` which provides set of methods and properties for accessing and manipulating the current context of execution.
88

99

10-
## HostContext
10+
## **HostContext**
1111
The `HostContext` class provides a wrapper around `ASGI` app parameters (`scope`, `receive` and `send`) and provides some methods that allows you choosing the appropriate context(e.g., HTTP or WebSockets).
1212

1313
For example, the `catch()` method of an **exception handlers** is called with an IHostContext.
@@ -44,7 +44,7 @@ class MyCustomExceptionHandler(IExceptionHandler):
4444

4545
```
4646

47-
## Switching to other Contexts
47+
## **Switching to other Contexts**
4848

4949
Currently, in Ellar you can only switch between `http` and `websocket` context. And each context has `get_client` method that returns context session.
5050

@@ -141,7 +141,7 @@ class IWebSocketConnectionHost(ABC):
141141
"""Returns WebSocket instance"""
142142
```
143143

144-
## ExecutionContext Class
144+
## **ExecutionContext Class**
145145
`ExecutionContext` extends `HostContext` and provides extra information like `Controller` class and controller `function`
146146
that will handler the current request.
147147

@@ -189,7 +189,7 @@ In this example, the `get_user` method is decorated with the `@get` decorator to
189189

190190
Once you have access to the `ExecutionContext` object, you can use its methods and properties to access information about the current request.
191191

192-
## Reflector and Metadata
192+
## **Reflector and Metadata**
193193
Ellar provides the ability to attach **custom metadata** to route handlers through the `@set_metadata()` decorator.
194194
We can then access this metadata from within our class to make certain decisions.
195195

@@ -239,3 +239,73 @@ class DogsController(ControllerBase):
239239

240240
!!! info
241241
It's important to note that `ExecutionContext` becomes available when there is route handler found to handle the current request.
242+
243+
To access the route's role(s) (custom metadata), we'll use the `Reflector` helper class, which is provided out of the box by the framework.
244+
`Reflector` can be injected into a class in the normal way:
245+
246+
```python
247+
# project_name/apps/dogs/guards.py
248+
from ellar.di import injectable
249+
from ellar.core import GuardCanActivate, IExecutionContext
250+
from ellar.services import Reflector
251+
252+
253+
@injectable()
254+
class RoleGuard(GuardCanActivate):
255+
def __init__(self, reflector: Reflector):
256+
self.reflector = reflector
257+
258+
async def can_activate(self, context: IExecutionContext) -> bool:
259+
roles = self.reflector.get('roles', context.get_handler())
260+
# request = context.switch_to_http_connection().get_request()
261+
# check if user in request object has role
262+
return 'user' in roles
263+
```
264+
265+
Next, we apply the `RoleGuard` to `DogsController`
266+
267+
```python
268+
# project_name/apps/dogs/controllers.py
269+
import typing
270+
from ellar.common import Body, Controller, post, set_metadata
271+
from ellar.core import ControllerBase
272+
from .schemas import CreateDogSerializer, DogListFilter
273+
from .guards import RoleGuard
274+
275+
def roles(*_roles: str) -> typing.Callable:
276+
return set_metadata('roles', list(_roles))
277+
278+
279+
@Controller('/dogs', guards=[RoleGuard, ])
280+
class DogsController(ControllerBase):
281+
@post()
282+
@roles('admin', 'is_staff')
283+
async def create(self, payload: CreateDogSerializer = Body()):
284+
result = payload.dict()
285+
result.update(message='This action adds a new dog')
286+
return result
287+
```
288+
289+
Also, since `RoleGuard` depends on `Reflector`, it has to be registered as a provider. And we do that in `DogsModule`:
290+
291+
```python
292+
# project_name/apps/dogs/module.py
293+
294+
from ellar.common import Module
295+
from ellar.core import ModuleBase
296+
from ellar.di import Container
297+
298+
from .controllers import DogsController
299+
from .guards import RoleGuard
300+
301+
302+
@Module(
303+
controllers=[DogsController],
304+
providers=[RoleGuard],
305+
)
306+
class DogsModule(ModuleBase):
307+
def register_providers(self, container: Container) -> None:
308+
# for more complicated provider registrations
309+
# container.register_instance(...)
310+
pass
311+
```

docs/img/math_router.png

211 KB
Loading
274 KB
Loading

docs/overview/functional_route.md

Whitespace-only changes.

docs/overview/module-router.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Module Router
2+
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.
6+
7+
## **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**
12+
13+
```python
14+
# project_name/apps/dogs/routers.py
15+
"""
16+
Define endpoints routes in python function fashion
17+
example:
18+
19+
my_router = ModuleRouter("/cats", tag="Cats", description="Cats Resource description")
20+
21+
@my_router.get('/')
22+
def index(request: Request):
23+
return {'detail': 'Welcome to Cats Resource'}
24+
"""
25+
from ellar.common import ModuleRouter
26+
27+
math_router = ModuleRouter('/math', tag='Math')
28+
29+
@math_router.get('/add')
30+
def addition(a:int, b:int):
31+
return a + b
32+
33+
34+
@math_router.get('/subtract')
35+
def subtraction(a:int, b:int):
36+
return a - b
37+
```
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.
40+
41+
Next, we have to make the `math_router` visible to the application
42+
43+
## **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.
45+
In the example provided above, the `math_router` would be registered under the `project_name/apps/dogs/module.py` file.
46+
47+
This registration process typically involves importing the `math_router` and then adding it to the list of `routers` in the `module.py` file.
48+
This allows the router to be recognized by the application and its routes to be available to handle requests.
49+
50+
```python
51+
52+
from ellar.common import Module
53+
from ellar.core import ModuleBase
54+
from ellar.di import Container
55+
56+
from .controllers import DogsController
57+
from .routers import math_router
58+
59+
60+
@Module(
61+
controllers=[DogsController],
62+
providers=[],
63+
routers=[math_router],
64+
)
65+
class DogsModule(ModuleBase):
66+
def register_providers(self, container: Container) -> None:
67+
# for more complicated provider registrations
68+
# container.register_instance(...)
69+
pass
70+
```
71+
72+
![math_router.png](../img/math_router.png)
73+
74+
75+
## **Accessing Other Request Object**
76+
In functional route handle, we can access request object and response object through custom decorators or type annotation as shown below.
77+
78+
### By Type Annotation
79+
Let's inject request and response object in `addition` route handler function from our previous example
80+
81+
```python
82+
from ellar.core import Request, Response
83+
from ellar.common import ModuleRouter
84+
85+
86+
math_router = ModuleRouter('/math', tag='Math')
87+
88+
@math_router.get('/add')
89+
def addition(request: Request, res: Response, a:int, b:int):
90+
res.headers['x-operation'] = 'Addition'
91+
return dict(is_request_object=isinstance(request, Request), is_response_object=isinstance(res, Response), operation_result=a + b)
92+
93+
```
94+
95+
### **By Custom decorators**
96+
You can also achieve the same result by using custom decorator.
97+
98+
```python
99+
from ellar.core import Request, Response
100+
from ellar.common import ModuleRouter, Req, Res
101+
102+
103+
math_router = ModuleRouter('/math', tag='Math')
104+
105+
@math_router.get('/add')
106+
def addition(*, request=Req(), res=Res(), a:int, b:int):
107+
res.headers['x-operation'] = 'Addition'
108+
return dict(is_request_object=isinstance(request, Request), is_response_object=isinstance(res, Response), operation_result=a + b)
109+
110+
```
111+
112+
![math_router_with_request_object.png](../img/math_router_with_request_object.png)
113+
114+
## **Inject Services**
115+
We can also inject service providers just like controller routes using the `Provide` function.
116+
117+
```python
118+
from ellar.core import Request, Response, IExecutionContext
119+
from ellar.common import ModuleRouter, Provide
120+
121+
122+
math_router = ModuleRouter('/math', tag='Math')
123+
124+
@math_router.get('/subtract')
125+
def subtraction(a:int, b:int, res=Provide(Response), req=Provide(Request), ctx=Provide(IExecutionContext)):
126+
res.headers['x-operation'] = 'Subtraction'
127+
return dict(
128+
is_request_object=isinstance(req, Request),
129+
is_response_object=isinstance(res, Response),
130+
is_context_object=isinstance(ctx, IExecutionContext),
131+
operation_result=a - b
132+
)
133+
134+
```

docs/templating/staticfiles.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
A static file is a type of file that does not change often and is not generated by a server-side script. Examples of static files include images, CSS and JavaScript files, audio and video files, and other types of media.
2+
3+
Static files in Ellar are served using the `StaticFiles` ASGI class, which is an extension of the Starlette `StaticFiles` ASGI class.
4+
This class uses the static files specified in the application's **modules** and **configuration**.
5+
6+
In addition, Ellar creates a route that mounts the static server at the `/static` path.
7+
The path can be modified by providing a new value for the `STATIC_MOUNT_PATH` configuration variable.
8+
9+
## **Configuring static files**
10+
11+
1. In your config file, define `STATIC_MOUNT_PATH`, for example:
12+
```python
13+
14+
class Config:
15+
STATIC_MOUNT_PATH = '/static'
16+
```
17+
18+
2. Store your static files in a folder called **static** in your module. For example **my_module/static/my_module/example.jpg**.
19+
3. In your templates, use the `url_for` with `static` and `path` parameter to build the URL for the given relative path using the configured in `STATIC_DIRECTORIES`, `STATIC_FOLDER_PACKAGES` or Module.
20+
```html
21+
<img src="{{url_for('static', path='my_module/example.jpg')}}" alt="My image">
22+
```
23+
OR, visit `/static/my_app/example.jpg`
24+
25+
26+
## **Static File in Modules**
27+
28+
Managing multiple sets of static files in larger projects can be challenging,
29+
but by organizing each set of static files within a specific module,
30+
it becomes easier to manage and maintain.
31+
This approach allows for clear organization and separation of static assets,
32+
making it more manageable in a large project.
33+
34+
In our previous project, within the `dogs` module folder, we can create a following directories, `my_static/dogs`.
35+
Inside this folder `my_static/dogs`, we can create a file named `example.txt`.
36+
This allows us to keep all of the static files related to the dogs module organized in one location `my_static`.
37+
38+
Next, we tell `DogModule` about our static folder.
39+
40+
```python
41+
# project_name/apps/dogs/module.py
42+
43+
from ellar.common import Module
44+
from ellar.core import ModuleBase
45+
from ellar.di import Container
46+
47+
from .controllers import DogsController
48+
49+
50+
@Module(
51+
controllers=[DogsController], static_folder='my_static'
52+
)
53+
class DogsModule(ModuleBase):
54+
def register_providers(self, container: Container) -> None:
55+
# for more complicated provider registrations
56+
# container.register_instance(...)
57+
pass
58+
```
59+
60+
## **Other Static Configurations**
61+
In addition to setting static directories within modules,
62+
it is also possible to manually specify additional static directories that are not located within a module by using the
63+
`STATIC_FOLDER_PACKAGES` and `STATIC_DIRECTORIES` variables in the application's configuration.
64+
These variables allow for even more flexibility in organizing and managing static files in a project.
65+
These directories will be served by the StaticFiles ASGI class along with the module-scoped static files.
66+
67+
### `STATIC_DIRECTORIES`
68+
`STATIC_DIRECTORIES` variable is a list of directories within the project that contain static files.
69+
These directories are not necessarily scoped to a specific module and can be used to serve static files from any location within the project.
70+
These directories can be added to the `STATIC_DIRECTORIES` list in the application's configuration.
71+
72+
```python
73+
STATIC_DIRECTORIES = ['project_name/static-files', 'project_name/path/to/static/files']
74+
```
75+
76+
### `STATIC_FOLDER_PACKAGES`
77+
`STATIC_FOLDER_PACKAGES` variable is a list of tuples that contain python packages that hold some static files.
78+
These packages should have a `static` folder and the **package name** should be passed as tuple `(package_name, package_path)`,
79+
**package_path** is the relative path of static folder.
80+
81+
```python
82+
83+
STATIC_FOLDER_PACKAGES = [('bootstrap', 'statics'), ('package-name', 'path/to/static/directory')]
84+
```
85+
86+
Static files will respond with "404 Not found" or "405 Method not allowed" responses for requests which do not match. In `HTML` mode if `404.html` file exists it will be shown as 404 response.

0 commit comments

Comments
 (0)