Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version4 #87

Merged
merged 26 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c898e44
Remove `set_connection_id()` and `set_validated_data()`
AliRn76 Feb 26, 2024
5c99303
Change usage of `config` attrs
AliRn76 Feb 26, 2024
86c1a22
Clean `Response` methods
AliRn76 Feb 26, 2024
4fc1c9e
Make `redis` connection `async`
AliRn76 Feb 26, 2024
8a6f1bb
Prevent user from adding routes with multiple path variables at same-…
AliRn76 Feb 27, 2024
6b49eda
Support `exclude` in `ModelSerializer`
AliRn76 Feb 28, 2024
788a6f5
Support `redis` in `Throttling`
AliRn76 Feb 28, 2024
d509e34
Improve `Monitoring` performance
AliRn76 Mar 1, 2024
ec7ee01
Support `optional_fields` in `ModelSerializer`
AliRn76 Mar 1, 2024
f5c2ac0
Add `generics.py`
AliRn76 Mar 1, 2024
6f25134
Add `How Panther Works!` diagram to `Readme`
AliRn76 Mar 4, 2024
b7caf29
Improve `Websocket` Architecture
AliRn76 Mar 4, 2024
206d516
Add `StreamingResponse`
AliRn76 Mar 6, 2024
722897f
Add `sort_fields`, `search_fields` and `filter_fields` to `ListAPI`
AliRn76 Mar 8, 2024
ab2550a
Add `Event` class
AliRn76 Mar 8, 2024
1be1649
Move `websocket listener` to its class
AliRn76 Mar 8, 2024
e61f418
Add `Redis` question to `create_cli`
AliRn76 Mar 8, 2024
c4ff92d
Move `set_password()` and `check_password()` to `BaseUser`
AliRn76 Mar 8, 2024
5c8b475
Add more tests to `test_response.py`
AliRn76 Mar 12, 2024
ffa9ab2
Support `Pagination` & `PantherDB.Cursor`
AliRn76 Mar 21, 2024
b8269d0
Support `TIMEZONE`
AliRn76 Mar 22, 2024
1dc31fb
Add `Events` Tests
AliRn76 Mar 22, 2024
104e23e
Listen to `http.disconnect` in `StreamingResponse`
AliRn76 Mar 22, 2024
7449b78
Fix `ID` Type
AliRn76 Mar 22, 2024
7ad1bed
Fix `ModelSerializer` attributes
AliRn76 Mar 24, 2024
7467a40
Improve Documentations
AliRn76 Mar 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
- Built-in API **Caching** System (In Memory, **Redis**)
- Built-in **Authentication** Classes
- Built-in **Permission** Classes
- Built-in Visual API **Monitoring** (In Terminal)
- Support Custom **Background Tasks**
- Support Custom **Middlewares**
- Support Custom **Throttling**
- Visual API **Monitoring** (In Terminal)
- Support **Function-Base** and **Class-Base** APIs
- It's One Of The **Fastest Python Frameworks**
---

Expand Down Expand Up @@ -176,6 +177,12 @@ $ pip install panther

---

### How Panther Works!

![diagram](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/diagram.png)

---

### Roadmap

![roadmap](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/roadmap.jpg)
Expand Down
28 changes: 18 additions & 10 deletions docs/docs/authentications.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
> <b>Type:</b> `str`
>
> <b>Default:</b> `None`

You can set your Authentication class in `configs`, now, if you set `auth=True` in `@API()`, Panther will use this class for authentication of every `API`, and put the `user` in `request.user` or `raise HTTP_401_UNAUTHORIZED`

We implemented a built-in authentication class which used `JWT` for authentication.
You can set Authentication class in your `configs`

Panther use it, to authenticate every API/ WS if `auth=True` and give you the user or `raise HTTP_401_UNAUTHORIZED`

The `user` will be in `request.user` in APIs and in `self.user` in WSs

---

We implemented 2 built-in authentication classes which use `JWT` for authentication.

But, You can use your own custom authentication class too.

---

### JWTAuthentication

Expand All @@ -17,7 +25,7 @@ This class will
- Get the `token` from `Authorization` header of request.
- Check the `Bearer`
- `decode` the `token`
- Find the matched `user` (It uses the `USER_MODEL`)
- Find the matched `user`

> `JWTAuthentication` is going to use `panther.db.models.BaseUser` if you didn't set the `USER_MODEL` in your `configs`

Expand Down Expand Up @@ -59,9 +67,7 @@ JWTConfig = {


#### Websocket Authentication
This class is very useful when you are trying to authenticate the user in websocket

Add this into your `configs`:
The `QueryParamJWTAuthentication` is very useful when you are trying to authenticate the user in websocket, you just have to add this into your `configs`:
```python
WS_AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
```
Expand All @@ -77,7 +83,9 @@ WS_AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
- Or raise `panther.exceptions.AuthenticationAPIError`


- Address it in `configs`
- `AUTHENTICATION = 'project_name.core.authentications.CustomAuthentication'`
- Add it into your `configs`
```python
AUTHENTICATION = 'project_name.core.authentications.CustomAuthentication'
```

> You can see the source code of JWTAuthentication [[here]](https://github.com/AliRn76/panther/blob/da2654ccdd83ebcacda91a1aaf51d5aeb539eff5/panther/authentications.py#L38)
> You can see the source code of JWTAuthentication [[here]](https://github.com/AliRn76/panther/blob/da2654ccdd83ebcacda91a1aaf51d5aeb539eff5/panther/authentications.py)
2 changes: 1 addition & 1 deletion docs/docs/background_tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Panther is going to run the `background tasks` as a thread in the background on
task = BackgroundTask(do_something, 'Ali', 26)
```

- > Default interval is 1.
- > Default of interval() is 1.

- > The -1 interval means infinite,

Expand Down
61 changes: 30 additions & 31 deletions docs/docs/class_first_crud.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
We assume you could run the project with [Introduction](https://pantherpy.github.io/#installation)

Now let's write custom API `Create`, `Retrieve`, `Update` and `Delete` for a `Book`:
Now let's write custom APIs for `Create`, `Retrieve`, `Update` and `Delete` a `Book`:

## Structure & Requirements
### Create Model
Expand Down Expand Up @@ -82,7 +82,7 @@ Now we are going to create a book on `post` request, We need to:

class BookAPI(GenericAPI):

def post(self):
async def post(self):
...
```

Expand All @@ -95,7 +95,7 @@ Now we are going to create a book on `post` request, We need to:

class BookAPI(GenericAPI):

def post(self, request: Request):
async def post(self, request: Request):
...
```

Expand All @@ -122,7 +122,7 @@ Now we are going to create a book on `post` request, We need to:
class BookAPI(GenericAPI):
input_model = BookSerializer

def post(self, request: Request):
async def post(self, request: Request):
...

```
Expand All @@ -138,7 +138,7 @@ Now we are going to create a book on `post` request, We need to:
class BookAPI(GenericAPI):
input_model = BookSerializer

def post(self, request: Request):
async def post(self, request: Request):
body: BookSerializer = request.validated_data
...
```
Expand All @@ -156,9 +156,9 @@ Now we are going to create a book on `post` request, We need to:
class BookAPI(GenericAPI):
input_model = BookSerializer

def post(self, request: Request):
async def post(self, request: Request):
body: BookSerializer = request.validated_data
Book.insert_one(
await Book.insert_one(
name=body.name,
author=body.author,
pages_count=body.pages_count,
Expand All @@ -180,15 +180,14 @@ Now we are going to create a book on `post` request, We need to:
class BookAPI(GenericAPI):
input_model = BookSerializer

def post(self, request: Request):
async def post(self, request: Request):
body: BookSerializer = request.validated_data
book = Book.insert_one(
book = await Book.insert_one(
name=body.name,
author=body.author,
pages_count=body.pages_count,
)
return Response(data=book, status_code=status.HTTP_201_CREATED)

```

> The response.data can be `Instance of Models`, `dict`, `str`, `tuple`, `list`, `str` or `None`
Expand All @@ -213,11 +212,11 @@ from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer

def post(self, request: Request):
async def post(self, request: Request):
...

def get(self):
books: list[Book] = Book.find()
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)

```
Expand Down Expand Up @@ -256,11 +255,11 @@ Assume we don't want to return field `author` in response:
input_model = BookSerializer
output_model = BookOutputSerializer

def post(self, request: Request):
async def post(self, request: Request):
...

def get(self):
books: list[Book] = Book.find()
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
```

Expand Down Expand Up @@ -292,11 +291,11 @@ class BookAPI(GenericAPI):
cache = True
cache_exp_time = timedelta(seconds=10)

def post(self, request: Request):
async def post(self, request: Request):
...

def get(self):
books: list[Book] = Book.find()
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)

```
Expand Down Expand Up @@ -329,11 +328,11 @@ class BookAPI(GenericAPI):
cache_exp_time = timedelta(seconds=10)
throttling = Throttling(rate=10, duration=timedelta(minutes=1))

def post(self, request: Request):
async def post(self, request: Request):
...

def get(self):
books: list[Book] = Book.find()
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)

```
Expand Down Expand Up @@ -383,8 +382,8 @@ For `retrieve`, `update` and `delete` API, we are going to

class SingleBookAPI(GenericAPI):

def get(self, book_id: int):
if book := Book.find_one(id=book_id):
async def get(self, book_id: int):
if book := await Book.find_one(id=book_id):
return Response(data=book, status_code=status.HTTP_200_OK)
else:
return Response(status_code=status.HTTP_404_NOT_FOUND)
Expand All @@ -406,11 +405,11 @@ from app.serializers import BookSerializer
class SingleBookAPI(GenericAPI):
input_model = BookSerializer

def get(self, book_id: int):
async def get(self, book_id: int):
...

def put(self, request: Request, book_id: int):
is_updated: bool = Book.update_one({'id': book_id}, request.validated_data.model_dump())
async def put(self, request: Request, book_id: int):
is_updated: bool = await Book.update_one({'id': book_id}, request.validated_data.model_dump())
data = {'is_updated': is_updated}
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
```
Expand All @@ -431,14 +430,14 @@ from app.models import Book
class SingleBookAPI(GenericAPI):
input_model = BookSerializer

def get(self, book_id: int):
async def get(self, book_id: int):
...

def put(self, request: Request, book_id: int):
async def put(self, request: Request, book_id: int):
...

def delete(self, book_id: int):
is_deleted: bool = Book.delete_one(id=book_id)
async def delete(self, book_id: int):
is_deleted: bool = await Book.delete_one(id=book_id)
if is_deleted:
return Response(status_code=status.HTTP_204_NO_CONTENT)
else:
Expand Down
20 changes: 19 additions & 1 deletion docs/docs/configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ Panther collect all the configs from your `core/configs.py` or the module you pa
> <b>Type:</b> `bool` (<b>Default:</b> `False`)

It should be `True` if you want to use `panther monitor` command
and see the monitoring logs
and watch the monitoring

If `True`:

- Log every request in `logs/monitoring.log`

_Requires [watchfiles](https://watchfiles.helpmanual.io) package._

---
### [LOG_QUERIES](https://pantherpy.github.io/log_queries)
> <b>Type:</b> `bool` (<b>Default:</b> `False`)
Expand All @@ -33,6 +35,8 @@ List of middlewares you want to use

Every request goes through `authentication()` method of this `class` (if `auth = True`)

_Requires [python-jose](https://python-jose.readthedocs.io/en/latest/) package._

_Example:_ `AUTHENTICATION = 'panther.authentications.JWTAuthentication'`

---
Expand Down Expand Up @@ -119,6 +123,8 @@ It will reformat your code on every reload (on every change if you run the proje

You may want to write your custom `ruff.toml` in root of your project.

_Requires [ruff](https://docs.astral.sh/ruff/) package._

Reference: [https://docs.astral.sh/ruff/formatter/](https://docs.astral.sh/ruff/formatter/)

_Example:_ `AUTO_REFORMAT = True`
Expand All @@ -133,4 +139,16 @@ We use it to create `database` connection
### [REDIS](https://pantherpy.github.io/redis)
> <b>Type:</b> `dict` (<b>Default:</b> `{}`)

_Requires [redis](https://redis-py.readthedocs.io/en/stable/) package._

We use it to create `redis` connection


---
### [TIMEZONE](https://pantherpy.github.io/timezone)
> <b>Type:</b> `str` (<b>Default:</b> `'UTC'`)

Used in `panther.utils.timezone_now()` which returns a `datetime` based on your `timezone`

And `panther.utils.timezone_now()` used in `BaseUser.date_created` and `BaseUser.last_login`

29 changes: 29 additions & 0 deletions docs/docs/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Startup Event

Use `Event.startup` decorator

```python
from panther.events import Event


@Event.startup
def do_something_on_startup():
print('Hello, I am at startup')
```

## Shutdown Event

```python
from panther.events import Event


@Event.shutdown
def do_something_on_shutdown():
print('Good Bye, I am at shutdown')
```


## Notice

- You can have **multiple** events on `startup` and `shutdown`
- Events can be `sync` or `async`
Loading
Loading