Skip to content

Commit

Permalink
Support WebsocketMiddleware #57
Browse files Browse the repository at this point in the history
  • Loading branch information
AliRn76 authored Dec 28, 2023
2 parents 06d9aea + fa1bb4a commit 3d7cff9
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 131 deletions.
119 changes: 82 additions & 37 deletions docs/docs/middlewares.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ And you can write your own custom middlewares too
## Structure of middlewares
`MIDDLEWARES` itself is a `list` of `tuples` which each `tuple` is like below:

(`Address of Middleware Class`, `kwargs as dict`)
(`Dotted Address of The Middleware Class`, `kwargs as dict`)


## Database Middleware
This middleware will create a `db` connection that uses in `ODM` or you can use it manually from:
This middleware will create a `db` connection which is used in `ODM` and you can use it manually too, it gives you a database connection:
```python
from panther.db.connection import db
```

We only support 2 database: `PantherDB` & `MongoDB`
We only support 2 database for now: `PantherDB` & `MongoDB`

- Address of Middleware: `panther.middlewares.db.DatabaseMiddleware`
- kwargs:
Expand All @@ -36,13 +36,13 @@ We only support 2 database: `PantherDB` & `MongoDB`
- Example of `PantherDB` (`Built-in Local Storage`):
```python
MIDDLEWARES = [
('panther.middlewares.db.DatabaseMiddleware', {'url': f'pantherdb://{BASE_DIR}/{DB_NAME}.pdb'}),
('panther.middlewares.db.DatabaseMiddleware', {'url': 'pantherdb://project_directory/database.pdb'}),
]
```
- Example of `MongoDB`:
```python
MIDDLEWARES = [
('panther.middlewares.db.DatabaseMiddleware', {'url': f'mongodb://{DB_HOST}:27017/{DB_NAME}'}),
('panther.middlewares.db.DatabaseMiddleware', {'url': 'mongodb://127.0.0.1:27017/example'}),
]
```

Expand All @@ -61,43 +61,88 @@ We only support 2 database: `PantherDB` & `MongoDB`
```

## Custom Middleware
Write a `class` and inherit from
```python
from panther.middlewares.base import BaseMiddleware
```
### Middleware Types
We have 3 type of Middlewares, make sure that you are inheriting from the correct one:
- `Base Middleware`: which is used for both `websocket` and `http` requests
- `HTTP Middleware`: which is only used for `http` requests
- `Websocket Middleware`: which is only used for `websocket` requests

### Write Custom Middleware
- Write a `class` and inherit from one of the classes below
```python
# For HTTP Requests
from panther.middlewares.base import HTTPMiddleware

# For Websocket Requests
from panther.middlewares.base import WebsocketMiddleware

# For Both HTTP and Websocket Requests
from panther.middlewares.base import BaseMiddleware
```

Then you can write your custom `before()` and `after()` methods
- Then you can write your custom `before()` and `after()` methods

- The `methods` should be `async`
- `before()` should have `request` parameter
- `after()` should have `response` parameter
- overwriting the `before()` and `after()` are optional
- The `methods` can get `kwargs` from their `__init__`
- The `methods` should be `async`
- `before()` should have `request` parameter
- `after()` should have `response` parameter
- overwriting the `before()` and `after()` are optional
- The `methods` can get `kwargs` from their `__init__`

### Custom Middleware Example
core/middlewares.py
```python
from panther.request import Request
from panther.response import Response
from panther.middlewares.base import BaseMiddleware
### Custom HTTP Middleware Example
- **core/middlewares.py**
```python
from panther.middlewares.base import HTTPMiddleware
from panther.request import Request
from panther.response import Response


class CustomMiddleware(BaseMiddleware):
class CustomMiddleware(HTTPMiddleware):

def __init__(self, something):
self.something = something
def __init__(self, something):
self.something = something

async def before(self, request: Request) -> Request:
print('Before Endpoint', self.something)
return request
async def before(self, request: Request) -> Request:
print('Before Endpoint', self.something)
return request

async def after(self, response: Response) -> Response:
print('After Endpoint', self.something)
return response
```
core/configs.py
```python
MIDDLEWARES = [
('core.middlewares.CustomMiddleware', {'something': 'hello-world'}),
]
```
async def after(self, response: Response) -> Response:
print('After Endpoint', self.something)
return response
```

- **core/configs.py**
```python
MIDDLEWARES = [
('core.middlewares.CustomMiddleware', {'something': 'hello-world'}),
]
```

### Custom HTTP + Websocket Middleware Example
- **core/middlewares.py**
```python
from panther.middlewares.base import BaseMiddleware
from panther.request import Request
from panther.response import Response
from panther.websocket import GenericWebsocket


class SayHiMiddleware(BaseMiddleware):

def __init__(self, name):
self.name = name

async def before(self, request: Request | GenericWebsocket) -> Request | GenericWebsocket:
print('Hello ', self.name)
return request

async def after(self, response: Response | GenericWebsocket) -> Response | GenericWebsocket:
print('Goodbye ', self.name)
return response
```

- **core/configs.py**
```python
MIDDLEWARES = [
('core.middlewares.SayHiMiddleware', {'name': 'Ali Rn'}),
]
```
3 changes: 3 additions & 0 deletions docs/docs/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### 3.4.0
- Support `WebsocketMiddleware`

### 3.3.2
- Add `content-length` to response header

Expand Down
2 changes: 1 addition & 1 deletion panther/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from panther.main import Panther # noqa: F401

__version__ = '3.3.2'
__version__ = '3.4.0'


def version():
Expand Down
15 changes: 11 additions & 4 deletions panther/_load_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from panther._utils import import_class
from panther.configs import JWTConfig, config
from panther.exceptions import PantherException
from panther.middlewares.base import WebsocketMiddleware, HTTPMiddleware
from panther.routings import finalize_urls, flatten_urls
from panther.throttling import Throttling

Expand Down Expand Up @@ -73,11 +74,13 @@ def load_default_cache_exp(configs: dict, /) -> timedelta | None:
return configs.get('DEFAULT_CACHE_EXP', config['default_cache_exp'])


def load_middlewares(configs: dict, /) -> list:
"""Collect The Middlewares & Set db_engine If One Of Middlewares Was For DB"""
def load_middlewares(configs: dict, /) -> dict:
"""
Collect The Middlewares & Set db_engine If One Of Middlewares Was For DB
And Return a dict with two list, http and ws middlewares"""
from panther.middlewares import BaseMiddleware

middlewares = []
middlewares = {'http': [], 'ws': []}

for middleware in configs.get('MIDDLEWARES') or []:
if not isinstance(middleware, list | tuple):
Expand All @@ -103,7 +106,11 @@ def load_middlewares(configs: dict, /) -> list:
if issubclass(Middleware, BaseMiddleware) is False:
raise _exception_handler(field='MIDDLEWARES', error='is not a sub class of BaseMiddleware')

middlewares.append(Middleware(**data)) # noqa: Py Argument List
middleware_instance = Middleware(**data)
if isinstance(middleware_instance, BaseMiddleware | HTTPMiddleware):
middlewares['http'].append(middleware_instance)
if isinstance(middleware_instance, BaseMiddleware | WebsocketMiddleware):
middlewares['ws'].append(middleware_instance)
return middlewares


Expand Down
30 changes: 29 additions & 1 deletion panther/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
import importlib
import logging
import re
import subprocess
import types
from collections.abc import Callable
from traceback import TracebackException
from uuid import uuid4

import orjson as json

from panther import status
from panther.exceptions import PantherException
from panther.file_handler import File


logger = logging.getLogger('panther')


Expand Down Expand Up @@ -150,3 +152,29 @@ def clean_traceback_message(exception: Exception) -> str:
tb.stack.remove(t)
_traceback = list(tb.format(chain=False))
return exception if len(_traceback) == 1 else f'{exception}\n' + ''.join(_traceback)


def reformat_code(base_dir):
try:
subprocess.run(['ruff', 'format', base_dir])
subprocess.run(['ruff', 'check', '--select', 'I', '--fix', base_dir])
except FileNotFoundError:
raise PantherException("Module 'ruff' not found, Hint: `pip install ruff`")


def check_function_type_endpoint(endpoint: types.FunctionType) -> Callable:
# Function Doesn't Have @API Decorator
if not hasattr(endpoint, '__wrapped__'):
logger.critical(f'You may have forgotten to use @API() on the {endpoint.__name__}()')
raise TypeError
return endpoint


def check_class_type_endpoint(endpoint: Callable) -> Callable:
from panther.app import GenericAPI

if not issubclass(endpoint, GenericAPI):
logger.critical(f'You may have forgotten to inherit from GenericAPI on the {endpoint.__name__}()')
raise TypeError

return endpoint.call_method
6 changes: 4 additions & 2 deletions panther/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ class Config(TypedDict):
default_cache_exp: timedelta | None
throttling: Throttling | None
secret_key: bytes | None
middlewares: list
reversed_middlewares: list
http_middlewares: list
ws_middlewares: list
reversed_http_middlewares: list
reversed_ws_middlewares: list
user_model: ModelMetaclass | None
authentication: ModelMetaclass | None
jwt_config: JWTConfig | None
Expand Down
Loading

0 comments on commit 3d7cff9

Please sign in to comment.