Skip to content

Commit 9a43625

Browse files
committed
review and updated provider, interceptor and module documents
1 parent 51510ed commit 9a43625

File tree

3 files changed

+137
-163
lines changed

3 files changed

+137
-163
lines changed

docs/overview/interceptors.md

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
# **Interceptors - [(AOP) technique](https://en.wikipedia.org/wiki/Aspect-oriented_programming){target="_blank"}**
22

3-
An interceptor is a class annotated with the `@injectable()` decorator and implements the EllarInterceptor interface.
4-
During request-response cycle, interceptors are called after middleware execution before route handler execution.
3+
An interceptor is a class marked with the `@injectable()` decorator and adhering to the **`EllarInterceptor`** interface.
4+
They execute additional logic before or after method invocation.
55

6-
Interceptors have a set of useful capabilities which are inspired by the [Aspect Oriented Programming (AOP) technique](https://en.wikipedia.org/wiki/Aspect-oriented_programming){target="_blank"} technique.
7-
They make it possible to:
6+
Inspired by the principles of [Aspect-Oriented Programming (AOP)](https://en.wikipedia.org/wiki/Aspect-oriented_programming){target="_blank"},
7+
interceptors offer several functionalities:
88

9-
- bind extra logic before / after method execution
10-
- transform the result returned from a function
11-
- transform the exception thrown from a function
12-
- extend the basic function behavior
13-
- completely override a function depending on specific conditions (e.g., for caching purposes)
9+
- Pre- and post-processing of method executions.
10+
- Transformation of return values.
11+
- Handling exceptions thrown during execution.
12+
- Extension of method behavior.
13+
- Conditional method override, useful for tasks like caching.
1414

1515
## **Basic**
16-
Each interceptor implements the `intercept()` method, which takes two arguments.
17-
The first one is the `ExecutionContext` instance (exactly the same object as for [guards](guards.md){target="_blank"}) and
18-
`next_interceptor` awaitable function that executes the next interceptor in the execution chain.
16+
Each interceptor class includes an `intercept()` method, accepting two parameters.
17+
The first parameter is an instance of the `ExecutionContext` class, which is identical to the object used for [guards](guards.md).
18+
The second parameter is a callable asynchronous function called `next_interceptor`,
19+
responsible for executing the subsequent interceptor in the execution sequence.
1920

2021
```python
2122
import typing as t
@@ -30,38 +31,27 @@ class EllarInterceptor(ABC):
3031
) -> t.Any:
3132
"""implementation comes here"""
3233
```
34+
3335
!!! note
34-
`intercept` function of interceptor class is an asynchronous function.
36+
The `intercept()` method within an interceptor class is an asynchronous function.
3537

3638
## **Execution context**
37-
The `ExecutionContext` adds several new helper methods that provide additional details about the current execution process.
38-
These details can be helpful in building more generic interceptors that can work across a broad set of controllers, methods, and execution contexts.
39-
Learn more about `ExecutionContext` [here](../basics/execution-context.md){target="_blank"}.
39+
The `ExecutionContext` introduces several auxiliary methods that offer further insights into the ongoing execution process.
40+
This additional information can be valuable for creating more versatile interceptors capable of functioning across various controllers, methods, and execution contexts.
41+
For further details on `ExecutionContext`, refer to the documentation [here](../basics/execution-context.md).
4042

4143
## **Next Interceptor Handler**
42-
The second argument, `next_interceptor`, in `intercept` of EllarInterceptor class is used to invoke the route handler method at some point in your interceptor.
43-
If you don't call the `next_interceptor` method in your implementation of the `intercept()` method, the route handler method won't be executed at all.
44-
45-
This approach means that the `intercept()` method effectively wraps the request/response cycle.
46-
As a result, you may implement custom logic **both before and after** the execution of the final route handler.
47-
It's clear that you can write code in your `intercept()` method that executes before calling `next_interceptor()`,
48-
but how do you affect what happens afterward? depending on the nature of the data returned by `next_interceptor()`,
49-
further manipulation can be done before final response to the client.
50-
51-
Using Aspect Oriented Programming terminology, the invocation of the route handler
52-
(i.e., calling `next_interceptor()`) is called a Pointcut, indicating that it's the point at which our
53-
additional logic is inserted.
54-
55-
Consider, for example, an incoming `POST /car` request. This request is destined for the `create()` handler
56-
defined inside the `CarController`. If an interceptor which does not call the `next_interceptor()`
57-
method is called anywhere along the way, the `create()` method won't be executed.
58-
Once `next_interceptor()` is called, the `create()` handler will be triggered. And once the response is returned,
59-
additional operations can be performed on the data returned, and a final result returned to the client.
44+
The `next_interceptor` parameter in the `intercept()` method of the `EllarInterceptor` class serves a crucial role in invoking the route handler method within your interceptor. Omitting the invocation of `next_interceptor` within your implementation of `intercept()` will result in the route handler method not being executed.
45+
46+
This mechanism essentially encapsulates the request/response cycle within the `intercept()` method. Consequently, you have the flexibility to incorporate custom logic both before and after the execution of the final route handler. While it's evident how to include code before calling `next_interceptor()`, influencing the behavior afterward depends on the data returned by `next_interceptor()`.
47+
48+
From the perspective of Aspect-Oriented Programming, the invocation of the route handler (i.e., calling `next_interceptor()`) represents a Pointcut, denoting the point at which additional logic is injected.
49+
50+
For instance, consider an incoming `POST /car` request targeting the `create()` handler in the `CarController`. If an interceptor fails to call `next_interceptor()` at any point, the `create()` method won't execute. However, once `next_interceptor()` is invoked, the `create()` handler proceeds. Subsequently, upon receiving the response, additional operations can be performed on the returned data before delivering the final result to the client.
6051

6152

6253
## **Aspect interception**
63-
The first use case we'll look at is to use an interceptor to log user interaction (e.g., storing user calls, asynchronously dispatching events or calculating a timestamp).
64-
We show a simple LoggingInterceptor below:
54+
Here's a simple example demonstrating the use of an interceptor to log user interactions. The **LoggingInterceptor** intercepts requests before and after the route handler execution to log relevant information such as start time, end time, and duration of execution.
6555

6656
```python
6757
import typing as t
@@ -70,10 +60,8 @@ import time
7060
from ellar.common import EllarInterceptor, IExecutionContext
7161
from ellar.di import injectable
7262

73-
7463
logger = logging.getLogger('ellar')
7564

76-
7765
@injectable()
7866
class LoggingInterceptor(EllarInterceptor):
7967
async def intercept(
@@ -82,17 +70,21 @@ class LoggingInterceptor(EllarInterceptor):
8270
logger.info('Before Route Handler Execution...')
8371
start_time = time.time()
8472

73+
# Invoke the next interceptor in the chain (or the route handler)
8574
res = await next_interceptor()
75+
76+
# Log after route handler execution
8677
logger.info(f'After Route Handler Execution.... {time.time() - start_time}s')
78+
8779
return res
8880
```
8981

90-
!!! hint
91-
Interceptors, like controllers, providers, guards, and so on, can inject dependencies through their `constructor`.
82+
This interceptor captures the timing of the request execution by recording the start time before invoking the route handler and calculating the duration after execution. It utilizes the logging module to output the relevant information.
83+
84+
Remember, like other components such as controllers and providers, interceptors can also inject dependencies through their constructor, enabling seamless integration with other parts of the application.
9285

9386
## **Binding interceptors**
94-
In order to set up the interceptor, we use the `@UseInterceptors()` decorator imported from the `ellar.common` package.
95-
Like **guards**, interceptors can be controller-scoped, method-scoped, or global-scoped.
87+
To set up an interceptor, we utilize the `@UseInterceptors()` decorator from the `ellar.common` package. Similar to guards, interceptors can be scoped at the controller level, method level, or globally.
9688

9789
```python
9890
from ellar.common import UseInterceptors, Controller
@@ -103,8 +95,7 @@ class CarController:
10395
...
10496
```
10597

106-
Note that we passed the LoggingInterceptor type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.
107-
As with guards, we can also pass an in-place instance:
98+
In the above code snippet, we apply the `UseInterceptors()` decorator to the `CarController` class, specifying `LoggingInterceptor` as the interceptor to be used. Note that we pass the type of the interceptor (not an instance), allowing the framework to handle instantiation and enabling dependency injection. Alternatively, we can directly pass an instance:
10899

109100
```python
110101
from ellar.common import UseInterceptors, Controller
@@ -115,10 +106,9 @@ class CarController:
115106
...
116107
```
117108

118-
As mentioned, the construction above attaches the interceptor to every handler declared by this controller.
119-
If we want to restrict the interceptor's scope to a single method, we simply apply the decorator at the method level.
109+
This construction attaches the interceptor to every handler declared within the controller. If we want to limit the scope of the interceptor to a specific method, we apply the decorator at the method level.
120110

121-
In order to set up a global interceptor, we use the use_global_interceptors() method of the Ellar application instance:
111+
For setting up a global interceptor, we utilize the `use_global_interceptors()` method of the Ellar application instance:
122112

123113
```python
124114
from ellar.app import AppFactory
@@ -129,8 +119,10 @@ app.use_global_interceptors(LoggingInterceptor())
129119
# app.use_global_interceptors(LoggingInterceptor)
130120
```
131121

122+
This approach ensures that the interceptor is applied to every request processed by the Ellar application, regardless of the controller or method handling the request.
123+
132124
## **Exception Handling**
133-
You can also handle exception through on the process of request/response cycle before it gets handled by system exception handlers.
125+
You can also manage exceptions during the request/response cycle before they are handled by system exception handlers.
134126

135127
```python
136128
class CustomException(Exception):
@@ -145,7 +137,12 @@ class InterceptCustomException(EllarInterceptor):
145137
try:
146138
return await next_interceptor()
147139
except CustomException as cex:
140+
# Access the response object from the context
148141
res = context.switch_to_http_connection().get_response()
142+
# Set the status code to 400 for a custom exception
149143
res.status_code = 400
144+
# Return a JSON response with the exception message
150145
return {"message": str(cex)}
151146
```
147+
148+
In the above code, the `InterceptCustomException` interceptor catches any `CustomException` raised during the execution of the request/response cycle. It then modifies the response object to set the status code to 400 and returns a JSON response containing the exception message. This allows for custom handling of exceptions within the interceptor before they are propagated to the system's exception handlers.

0 commit comments

Comments
 (0)