Skip to content

Commit 3d6653f

Browse files
authored
Merge pull request #64 from eadwinCode/guard_doc
Guard Documentation
2 parents c881c51 + 218aa4e commit 3d6653f

File tree

22 files changed

+352
-145
lines changed

22 files changed

+352
-145
lines changed

docs/basics/execution-context.md

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -194,20 +194,20 @@ Ellar provides the ability to attach **custom metadata** to route handlers throu
194194
We can then access this metadata from within our class to make certain decisions.
195195

196196
```python
197-
# project_name/apps/dogs/controllers.py
197+
# project_name/apps/cars/controllers.py
198198

199199
from ellar.common import Body, Controller, post, set_metadata
200200
from ellar.core import ControllerBase
201-
from .schemas import CreateDogSerializer, DogListFilter
201+
from .schemas import CreateCarSerializer
202202

203203

204-
@Controller('/dogs')
205-
class DogsController(ControllerBase):
204+
@Controller('/car')
205+
class CarController(ControllerBase):
206206
@post()
207207
@set_metadata('role', ['admin'])
208-
async def create(self, payload: CreateDogSerializer = Body()):
208+
async def create(self, payload: CreateCarSerializer = Body()):
209209
result = payload.dict()
210-
result.update(message='This action adds a new dog')
210+
result.update(message='This action adds a new car')
211211
return result
212212
```
213213

@@ -216,24 +216,24 @@ to the `create()` method. While this works, it's not good practice to use `@set_
216216
Instead, create your own decorators, as shown below:
217217

218218
```python
219-
# project_name/apps/dogs/controllers.py
219+
# project_name/apps/cars/controllers.py
220220
import typing
221221
from ellar.common import Body, Controller, post, set_metadata
222222
from ellar.core import ControllerBase
223-
from .schemas import CreateDogSerializer, DogListFilter
223+
from .schemas import CreateCarSerializer
224224

225225

226226
def roles(*_roles: str) -> typing.Callable:
227227
return set_metadata('roles', list(_roles))
228228

229229

230-
@Controller('/dogs')
231-
class DogsController(ControllerBase):
230+
@Controller('/car')
231+
class CarController(ControllerBase):
232232
@post()
233233
@roles('admin', 'is_staff')
234-
async def create(self, payload: CreateDogSerializer = Body()):
234+
async def create(self, payload: CreateCarSerializer = Body()):
235235
result = payload.dict()
236-
result.update(message='This action adds a new dog')
236+
result.update(message='This action adds a new car')
237237
return result
238238
```
239239

@@ -244,7 +244,7 @@ To access the route's role(s) (custom metadata), we'll use the `Reflector` helpe
244244
`Reflector` can be injected into a class in the normal way:
245245

246246
```python
247-
# project_name/apps/dogs/guards.py
247+
# project_name/apps/cars/guards.py
248248
from ellar.di import injectable
249249
from ellar.core import GuardCanActivate, IExecutionContext
250250
from ellar.services import Reflector
@@ -259,53 +259,34 @@ class RoleGuard(GuardCanActivate):
259259
roles = self.reflector.get('roles', context.get_handler())
260260
# request = context.switch_to_http_connection().get_request()
261261
# check if user in request object has role
262+
if not roles:
263+
return True
262264
return 'user' in roles
263265
```
264266

265-
Next, we apply the `RoleGuard` to `DogsController`
267+
Next, we apply the `RoleGuard` to `CarController`
266268

267269
```python
268-
# project_name/apps/dogs/controllers.py
270+
# project_name/apps/cars/controllers.py
269271
import typing
270-
from ellar.common import Body, Controller, post, set_metadata
272+
from ellar.common import Body, Controller, post, set_metadata, Guards
271273
from ellar.core import ControllerBase
272-
from .schemas import CreateDogSerializer, DogListFilter
274+
from .schemas import CreateCarSerializer
273275
from .guards import RoleGuard
274276

275277
def roles(*_roles: str) -> typing.Callable:
276278
return set_metadata('roles', list(_roles))
277279

278280

279-
@Controller('/dogs', guards=[RoleGuard, ])
280-
class DogsController(ControllerBase):
281+
@Controller('/car')
282+
@Guards(RoleGuard)
283+
class CarController(ControllerBase):
281284
@post()
282285
@roles('admin', 'is_staff')
283-
async def create(self, payload: CreateDogSerializer = Body()):
286+
async def create(self, payload: CreateCarSerializer = Body()):
284287
result = payload.dict()
285-
result.update(message='This action adds a new dog')
288+
result.update(message='This action adds a new car')
286289
return result
287290
```
288291

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-
```
292+
Also, since `RoleGuard` is marked as `injectable`, EllarInjector service will be able to resolve `RoleGuard` without `RoleGuard` registered as a provider.

docs/handling-response/response.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,3 @@ class ItemsController(ControllerBase):
203203
def me(self):
204204
return PlainTextResponse("some text response.", status_code=200)
205205
```
206-
207-
## using serialize_object function

docs/index.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,29 @@
1515
Ellar is a lightweight ASGI framework for building efficient and scalable server-side python applications.
1616
It supports both OOP (Object-Oriented Programming) and FP (Functional Programming)
1717

18-
19-
Ellar is built around [Starlette (ASGI toolkit)](https://www.starlette.io/) which processes all the HTTP requests and background tasks. Although, there is a high level
20-
of abstraction, some concepts of Starlette are still supported.
18+
Ellar is based on [Starlette (ASGI toolkit)](https://www.starlette.io/), a lightweight ASGI framework/toolkit well-suited for developing asynchronous web services in Python.
19+
And 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.
20+
If you are familiar with these frameworks, you will find it easy to understand and use Ellar.
2121

2222
## Inspiration
23-
Ellar was heavily inspired by [NestJS](https://docs.nestjs.com/) in its simplicity in usage while managing complex project structures and applications.
24-
It also adopted some concepts of [FastAPI](https://fastapi.tiangolo.com/) in handling request parameters and data serialization with pydantic.
25-
With that said, the aim of Ellar focuses on a high level of abstraction of framework APIs, project structures, architectures, and speed of handling requests.
23+
Ellar was deeply influenced by [NestJS](https://docs.nestjs.com/) for its ease of use and ability to handle complex project structures and applications.
24+
Additionally, it took some concepts from [FastAPI](https://fastapi.tiangolo.com/) in terms of request parameter handling and data serialization with Pydantic.
25+
26+
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,
27+
the ability to adapt to any desired software architecture, and ultimately, speedy request handling.
2628

2729
## Installation
2830
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.
2931
The scaffolded project is more like a guide to project setup.
3032

3133
```shell
32-
$(venv) pip install ellar[standard]
33-
$(venv) ellar new project-name
34+
$(venv) pip install ellar
3435
```
3536

36-
### NB:
37-
Some shells may treat square braces (`[` and `]`) as special characters. If that's the case here, then use a quote around the characters to prevent unexpected shell expansion.
37+
After that, lets create a new project.
38+
Run the command below and change the `project-name` with whatever name you decide.
3839
```shell
39-
pip install "ellar[standard]"
40+
$(venv) ellar new project-name
4041
```
4142

4243
then, start the app with:
@@ -60,7 +61,7 @@ Open your browser and navigate to [`http://localhost:8000/`](http://localhost:80
6061
- `CORS, GZip, Static Files, Streaming responses`
6162

6263
## Dependency Summary
63-
- `Python >= 3.6`
64+
- `Python >= 3.7`
6465
- `Starlette`
6566
- `Pydantic`
6667
- `Injector`

docs/overview/custom_decorators.md

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
Ellar provides a variety of function decorators in the `ellar.common` python module that can be used to modify the behavior of route functions.
3+
34
These decorators can be used to change the response type of a route function, add filters to the response schema, define the OPENAPI context, and more.
45
In general, these decorators can help to simplify and streamline the process of creating routes.
56

@@ -57,12 +58,14 @@ async def on_connect(self, websocket: WebSocket, code: int):
5758
## **Non Route Function Parameters Decorators**
5859
We discussed decorators that are used to define route function parameter dependencies in Ellar.
5960
These decorators, such as `Query`, `Form`, and `Body`, etc. are pydantic models used to specify the expected parameters for a route function.
61+
6062
However, there are also some route parameters that are **system** dependent, such as the `request` or `websocket` object, and the `response` object.
6163
These parameters are resolved by the application and supplied to the route function when needed, and are not specified with pydantic models or user input.
6264

6365
### Provide(Type)
6466
The **Provide(Type)** decorator is used to resolve a service provider and inject it into a route function parameter.
6567
This can be useful when using the ModuleRouter feature in Ellar.
68+
6669
It allows for easy injection of services into route functions, making it easier to manage dependencies and improve code organization.
6770
This can be useful for resolving database connections, external APIs, or other resources that are used by the route function.
6871

@@ -108,6 +111,7 @@ def example_endpoint(ctx = Context()):
108111

109112
In this example, the example_endpoint function is decorated with the **Context()** decorator, which injects the current `IExecutionContext` object into the `ctx` parameter of the function.
110113
The `IExecutionContext` object provides access to various resources and information related to the current execution context, such as the current HTTP connection, query parameters, and more.
114+
111115
In this example, the `switch_to_http_connection()` method is used to access the current HTTP connection and the `get_client()` method is used to get the client object for the connection.
112116
The `query_params` attribute of the client object is then accessed and included in the response returned by the endpoint.
113117

@@ -167,14 +171,11 @@ async def example_endpoint(ws = Ws()):
167171
The above code creates a WebSocket route '/test-ws' and when a client connects to this route,
168172
the `example_endpoint` function is executed. The `Ws` decorator injects the current `WebSocket` object to the `ws` parameter of the function, which can then be used to interact with the WebSocket connection, such as accepting the connection and sending data to the client.
169173

170-
### Host
171-
**Host()** decorator injects current client host address to route function parameter.
172-
173-
### Session
174-
**Session()** decorator injects current Session object to route function parameter.
174+
The same conditions and examples applies for:
175175

176-
### Http
177-
**Http()** decorator injects current HTTP connection object to route function parameter.
176+
- **Host()** decorator injects current client host address to route function parameter.
177+
- **Session()** decorator injects current Session object to route function parameter. This requires [SessionMiddleware](https://www.starlette.io/middleware/#sessionmiddleware) module from Starlette added in application middleware and also `SessionMiddleware` module depends on [itsdangerous](https://pypi.org/project/itsdangerous/) package.
178+
- **Http()** decorator injects current HTTP connection object to route function parameter.
178179

179180
## **Creating a Custom Parameter Decorators**
180181
You can still create your own route parameter decorators that suits your need. You simply need to follow a contract, `NonParameterResolver`, and override the resolve function.
@@ -203,6 +204,7 @@ class UserParam(NonParameterResolver):
203204

204205
This example defines a custom decorator called `UserParam` that inherits from `NonParameterResolver`.
205206
The `resolve` method is overridden to extract the user from the current `IExecutionContext`'s request.
207+
206208
If the user is found, it is returned as a dict with the key as the `parameter_name` of the decorator, along with an empty list of errors.
207209
If no user is found, an empty dict and a list of errors containing an ErrorWrapper object is returned.
208210

@@ -234,6 +236,7 @@ def index(self):
234236

235237
In the example, the index function is decorated with the `render` decorator,
236238
which will return a 200 status code and HTML content from my_template.
239+
237240
The return object from the index function will be used as the templating context for `my_template` during the template rendering process.
238241
This allows the function to pass data to the template and have it rendered with the provided context, the rendered template will be the response body.
239242

@@ -379,15 +382,16 @@ See [Pydantic Model Export](https://docs.pydantic.dev/usage/exporting_models/#mo
379382
### VERSION
380383
**@version()** is a decorator that provides endpoint versioning for a route function.
381384
This decorator allows you to specify the version of the endpoint that the function is associated with.
385+
382386
Based on the versioning scheme configuration in the application, versioned route functions are called. This can be useful for maintaining backward compatibility, or for rolling out new features to different versions of an application.
383387
More information on how to use this decorator can be found in the [Versioning documentation]()
384388

385389
A quick example on how to use `version` decorator:
386390
```python
387-
from ellar.common import post, version
391+
from ellar.common import post, Version
388392

389393
@post("/create", name='v2_v3_list')
390-
@version('2', '3')
394+
@Version('2', '3')
391395
async def get_item_v2_v3(self):
392396
return {'message': 'for v2 and v3 request'}
393397
```
@@ -397,16 +401,18 @@ This indicates that the `get_item_v2_v3` route function will handle version 2 an
397401
This allows for multiple versions of the same endpoint to be handled by different route functions, each with their own logic and implementation.
398402

399403
### GUARDS
400-
**@guards()** is a decorator that applies a protection class of type GuardCanActivate to a route function.
401-
These protection classes have a can_execute function that is called to determine whether a route function should be executed.
402-
This decorator allows you to apply certain conditions or checks before a route function is executed, such as authentication or authorization checks.
404+
**@Guards()** is a decorator that applies a protection class of type `GuardCanActivate` to a route function.
405+
These protection classes have a `can_execute` function that is called to determine whether a route function should be executed.
406+
407+
This decorator allows you to apply certain conditions or checks before a route function is executed, such as `authentication` or `authorization` checks.
403408
This can help to ensure that only authorized users can access certain resources.
409+
404410
More information on how to use this decorator can be found in the [Guard Documentation]()
405411

406-
A quick example on how to use `guards` decorator:
412+
A quick example on how to use `Guards` decorator:
407413
```python
408414
import typing as t
409-
from ellar.common import get, guards
415+
from ellar.common import get, Guards
410416
from ellar.core.guard import APIKeyQuery
411417
from ellar.core.connection import HTTPConnection
412418

@@ -419,14 +425,16 @@ class MyAPIKeyQuery(APIKeyQuery):
419425

420426

421427
@get("/")
422-
@guards(MyAPIKeyQuery(), )
428+
@Guards(MyAPIKeyQuery(), )
423429
async def get_guarded_items(self):
424430
return {'message': 'worked fine with `key`=`supersecret`'}
425431
```
426-
The `guards` decorator, like the `version` decorator, takes a list of values as an argument.
432+
The `Guards` decorator, like the `version` decorator, takes a list of values as an argument.
427433
During a request, the provided guards are called in the order in which they are provided.
434+
428435
This allows you to apply multiple guards to a single route function and have them executed in a specific order.
429436
This is useful for applying multiple levels of security or access control to a single endpoint.
437+
430438
Each guard class has a `can_execute` function that is called in the order specified by the decorator, if any of the guard's `can_execute` function returns False, the route function will not be executed.
431439

432440
## **Command Decorators**

0 commit comments

Comments
 (0)