Skip to content

Commit e97b5d5

Browse files
authored
Merge pull request #91 from eadwinCode/background_tasks
Background Task Feature
2 parents 941b3d0 + 2d361c9 commit e97b5d5

29 files changed

+314
-97
lines changed

docs/background-task.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,52 @@
1-
## Coming Soon
1+
# **Background Tasks**
2+
3+
Background tasks are tasks attached to a response and processed after a response has been sent to the client. An example of
4+
this kind of task could be email notifications sent after performing an action.
5+
6+
## **Adding BackgroundTask**
7+
In Ellar, you can access the **response** object and set a `background` parameter.
8+
```python
9+
from starlette.background import BackgroundTask
10+
from ellar.common import ModuleRouter, get, Res
11+
12+
router = ModuleRouter('/background-test')
13+
14+
async def send_welcome_email(email):
15+
print(f'Send Welcome Email Task Called with {email}')
16+
17+
@router.post('/signup')
18+
def sign_up(username: str, password: str, email: str, res=Res()):
19+
res.background = BackgroundTask(send_welcome_email, email=email)
20+
return {'status': 'Signup successful'}
21+
```
22+
In above construct, we created `BackgroundTask` around `send_welcome_email` function and passed to it some `*args/**kwargs `required to invoke the wrapped function.
23+
After the response has been sent to the client, the background function[`send_welcome_email`] will be called and there will be a print on the server log.
24+
25+
## **Using BackgroundTasks**
26+
`BackgroundTasks` is another class from Starlette useful for adding multiple background tasks to a response.
27+
Unlike the previous construct, `BackgroundTasks` can be injected into your route function by type annotation.
28+
29+
For example:
30+
```python
31+
from starlette.background import BackgroundTasks
32+
from ellar.common import ModuleRouter, get, Res
33+
34+
router = ModuleRouter('/background-test')
35+
36+
def another_background_task(parameter):
37+
print(f'Another Background task called with "{parameter}"')
38+
39+
async def send_welcome_email(email):
40+
print(f'Send Welcome Email Task Called with "{email}"')
41+
42+
@router.post('/signup')
43+
def sign_up(username: str, password: str, email: str, tasks: BackgroundTasks):
44+
tasks.add_task(send_welcome_email, email=email)
45+
tasks.add_task(another_background_task, 'another_background_task parameter')
46+
return {'status': 'Signup successful'}
47+
```
48+
49+
During request response cycle, `BackgroundTasks` will be resolved made available to the route handler function.
50+
51+
!!! hint
52+
The tasks are executed in order. In case one of the tasks raises an exception, the following tasks will not get the opportunity to be executed.

docs/basics/execution-context.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# **Execution Context**
12
Execution context refers to the current context of execution, or the environment in which a specific piece of code is running.
23
It contains information about the current request, the current response in the case of http connection, and the current state of the application.
34

docs/basics/testing.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# **Testing**
12
Automated testing is the practice of using software tools to automatically run tests on a software application or system,
23
rather than relying on manual testing by humans. It is considered an essential part of software development as it
34
helps increase productivity, ensure quality and performance goals are met, and provide faster feedback loops to developers.
@@ -8,7 +9,7 @@ Ellar aims to encourage the use of development best practices, including effecti
89
These features include:
910

1011
- automatically generated default unit tests files for components testing
11-
- offering a util, `TestClientFactory`, that constructs an isolated module/application setup
12+
- offering a util, `Test` Factory class, that constructs an isolated module/application setup
1213
- making the Ellar dependency injection system accessible in the testing environment for convenient component mocking.
1314

1415
Ellar is compatible with `unittest` and `pytest` testing frameworks in python but in this documentation, we will be using `pytest`.

docs/basics/versioning.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# **Versioning**
12
Versioning allows for the existence of multiple versions of controllers or individual routes within the same application,
23
which can be useful when making changes that may break previous versions.
34
This allows developers to support older versions of the application while still making necessary updates.

docs/caching.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# **Caching**
12
Caching refers to the process of storing frequently accessed data in a temporary storage area called a `cache`,
23
in order to speed up access to that data in the future.
34

@@ -6,7 +7,7 @@ By caching data in a faster, more local storage location, the system can quickly
67

78
In Ellar, we provided several cache backends interface that interacts with different cache types to assist in cache endpoint responses or other relevant data.
89

9-
## Setting up the cache
10+
## **Setting up the cache**
1011
It's very simple to set up cache in Ellar but the crucial part is picking the cache type that is suitable for your application
1112
because some cache type behave differently and perform better and faster than others.
1213

@@ -296,7 +297,7 @@ class DevelopmentConfig(ConfigDefaultTypesMixin):
296297
}
297298
```
298299

299-
## Cache Arguments
300+
## **Cache Arguments**
300301

301302
You can customize the behavior of each caching backend in Django by passing additional arguments when you configure the cache. The valid arguments that can be passed to each backend are as follows:
302303

@@ -323,7 +324,7 @@ class DevelopmentConfig(ConfigDefaultTypesMixin):
323324
}
324325
```
325326

326-
## Setting up More than One Cache Backend
327+
## **Setting up More than One Cache Backend**
327328
To set up multiple cache backends in Django, you can add additional entries to the `CACHES` variable in your `config.py` file.
328329
The `default` cache backend is typically defined first, followed by any additional cache backends you want to configure.
329330

@@ -343,7 +344,7 @@ class DevelopmentConfig(ConfigDefaultTypesMixin):
343344
}
344345
```
345346

346-
## CacheService (ICacheService)
347+
## **CacheService (ICacheService)**
347348
Ellar does not provide cache backends directly, but instead offers a caching service that manages all the configured cache backends in your application.
348349
The `CacheService` class serves as a wrapper for these cache backends and provides a uniform interface for interacting with them.
349350

@@ -374,7 +375,7 @@ The CacheService class provides methods like:
374375

375376
These methods are available for each of the configured cache backends and can be used interchangeably with any backend.
376377

377-
## Injecting CacheService
378+
## **Injecting CacheService**
378379
`CacheService` is a core service registered in `EllarInjector` and can be injected as every other service.
379380

380381
For example, lets make `CacheService` available in our route function.
@@ -411,7 +412,7 @@ For example, lets make `CacheService` available in our route function.
411412
```
412413

413414

414-
## Using Cache Decorator
415+
## **Using Cache Decorator**
415416
Ellar provides a cache decorator that can be used to cache the responses of route functions. The cache decorator can be applied to a route function to automatically cache its response data for a specified amount of time.
416417

417418
The cache decorator takes the following arguments:
@@ -425,8 +426,8 @@ The cache decorator takes the following arguments:
425426
We can rewrite the above example using `cache` decorator:
426427
=== "Synchronous Route Function"
427428
```python
428-
from ellar.common import get, cache
429-
429+
from ellar.common import get
430+
from ellar.cache import cache
430431
...
431432
@get('/cache-test')
432433
@cache(ttl=300, version='v1', key_prefix='project_name')
@@ -446,7 +447,7 @@ We can rewrite the above example using `cache` decorator:
446447
return processed_value
447448
```
448449

449-
### Adding Custom key gen function for cache Decorator
450+
### **Adding Custom key gen function for cache Decorator**
450451
By default, the `cache` decorator combines the route function's URL and the specified `key_prefix` value to generate the cache key used to store the response data.
451452
However, you can customize this behavior by providing a `make_key_callback` function to the cache decorator.
452453

@@ -455,7 +456,8 @@ The `make_key_callback` function takes an `ExecutionContext` instance (which con
455456
Here's an example of how to use a custom `make_key_callback` function with the cache decorator:
456457
=== "Synchronous Route Function"
457458
```python
458-
from ellar.common import get, cache
459+
from ellar.common import get
460+
from ellar.cache import cache
459461
from ellar.core import ExecutionContext
460462
from ellar.common.helper import get_name
461463

@@ -475,7 +477,8 @@ Here's an example of how to use a custom `make_key_callback` function with the c
475477
=== "Asynchronous Route Function"
476478

477479
```python
478-
from ellar.common import get, cache
480+
from ellar.common import get
481+
from ellar.cache import cache
479482
from ellar.core import ExecutionContext
480483
from ellar.common.helper import get_name
481484

docs/configurations.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# **Application Configurations**
12

23
The `config.py` file contains all the configuration necessary in bootstrapping ellar application.
34

docs/handling-response/response-model.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,23 @@ Let's see different `ResponseModel` available in Ellar and how you can create on
135135
### **ResponseModel**
136136
Response model that manages rendering of other response types.
137137

138-
- Location: `ellar.core.response.model.base.ResponseModel`
138+
- Location: `ellar.common.responses.models.ResponseModel`
139139
- response_type: `Response`
140140
- model_field_or_schema: `None`
141141
- media_type: `text/plain`
142142

143143
### **JSONResponseModel**
144144
Response model that manages `JSON` response.
145145

146-
- Location: `ellar.core.response.model.json.JSONResponseModel`
146+
- Location: `ellar.common.responses.models.json.JSONResponseModel`
147147
- response_type: `JSONResponse` OR `config.DEFAULT_JSON_CLASS`
148148
- model_field_or_schema: `Required`
149149
- media_type: `application/json`
150150

151151
### **HTMLResponseModel**
152152
Response model that manages `HTML` templating response. see [`@render`]() decorator.
153153

154-
- Location: `ellar.core.response.model.html.HTMLResponseModel`
154+
- Location: `ellar.common.responses.models.html.HTMLResponseModel`
155155
- response_type: `TemplateResponse`
156156
- model_field_or_schema: `None`
157157
- media_type: `text/html`
@@ -160,7 +160,7 @@ Response model that manages `HTML` templating response. see [`@render`]() decora
160160
### **FileResponseModel**
161161
Response model that manages `FILE` response. see [`@file`]() decorator.
162162

163-
- Location: `ellar.core.response.model.file.FileResponseModel`
163+
- Location: `ellar.common.responses.models.file.FileResponseModel`
164164
- response_type: `FileResponse`
165165
- model_field_or_schema: `None`
166166
- media_type: `Required`
@@ -169,7 +169,7 @@ Response model that manages `FILE` response. see [`@file`]() decorator.
169169
### **StreamingResponseModel**
170170
Response model that manages `STREAMING` response. see [`@file`]() decorator.
171171

172-
- Location: `ellar.core.response.model.file.StreamingResponseModel`
172+
- Location: `ellar.common.responses.models.file.StreamingResponseModel`
173173
- response_type: `StreamingResponse`
174174
- model_field_or_schema: `None`
175175
- media_type: `Required`
@@ -178,7 +178,7 @@ Response model that manages `STREAMING` response. see [`@file`]() decorator.
178178
### **EmptyAPIResponseModel**
179179
Default `ResponseModel` applied when no response is defined.
180180

181-
- Location: `ellar.core.response.model.html.EmptyAPIResponseModel`
181+
- Location: `ellar.common.responses.models.json.EmptyAPIResponseModel`
182182
- response_type: `JSONResponse` OR `config.DEFAULT_JSON_CLASS`
183183
- model_field_or_schema: `dict`
184184
- media_type: `application/json`

docs/mount.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,63 @@
1-
## Coming Soon
1+
# **Mount**
2+
In Starlette, `Mount` is used to mount sub-routes and ASGI apps or WSGI apps. The same is applicable in Ellar.
3+
4+
Let's see how to mount sub-routes in ellar
5+
```python
6+
7+
from starlette.routing import Mount
8+
from ellar.common.routing import RouteOperation
9+
from ellar.common import Req
10+
11+
def users(request=Req()):
12+
return "List of users"
13+
14+
def user(username: str, request=Req()):
15+
return f"Users Profile of {username}"
16+
17+
18+
mount = Mount(
19+
path='/users',
20+
routes=[
21+
RouteOperation(path='/', endpoint=users, methods=['GET', 'POST'], response={200: str}),
22+
RouteOperation(path='/{username}', endpoint=user, methods=['GET', 'POST'], response={200: str})
23+
]
24+
)
25+
```
26+
In the construct above, we have created a starlette-like example of `Mount` with a base path `/users` and with two endpoints,
27+
`/` to get list of users and `/username` to a users profile.
28+
29+
### **Mount with Ellar**
30+
Now, we have a `mount` instance for the previous code construct, to get it to work in ellar, we need to register it to a **Module**.
31+
32+
For example,
33+
```python
34+
from ellar.common import Module
35+
from .path_to_mount import mount
36+
37+
38+
@Module(routers=[mount])
39+
class ApplicationModule:
40+
pass
41+
42+
```
43+
44+
## **[Applying Middleware to Mount](https://www.starlette.io/middleware/#applying-middleware-to-mounts){target="_blank"}**
45+
Just like in every other ASGI app, middlewares can be added to `Mount` during its instantiation.
46+
47+
For example,
48+
```python
49+
...
50+
from starlette.middleware import Middleware
51+
from starlette.middleware.gzip import GZipMiddleware
52+
53+
54+
mount = Mount(
55+
path='/users',
56+
routes=[
57+
RouteOperation(path='/', endpoint=users, methods=['GET', 'POST'], response={200: str}),
58+
RouteOperation(path='/{username}', endpoint=user, methods=['GET', 'POST'], response={200: str})
59+
],
60+
middleware=[Middleware(GZipMiddleware)]
61+
)
62+
```
63+
Checkout this [documentation from starlette](https://www.starlette.io/middleware/#applying-middleware-to-mounts){target="_blank"} on some conditions to using `middleware` on Mount

docs/overview/controllers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
# **Controllers**
22
The Controller is responsible for handling incoming requests and returning responses to the client.
33
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.
44

0 commit comments

Comments
 (0)