panther 3.3.1__tar.gz → 4.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panther-4.1.3/PKG-INFO +204 -0
- panther-4.1.3/README.md +173 -0
- {panther-3.3.1 → panther-4.1.3}/panther/__init__.py +1 -1
- panther-4.1.3/panther/_load_configs.py +263 -0
- {panther-3.3.1 → panther-4.1.3}/panther/_utils.py +53 -70
- panther-4.1.3/panther/app.py +213 -0
- panther-4.1.3/panther/authentications.py +166 -0
- {panther-3.3.1 → panther-4.1.3}/panther/background_tasks.py +25 -14
- {panther-3.3.1 → panther-4.1.3}/panther/base_request.py +43 -21
- panther-4.1.3/panther/base_websocket.py +278 -0
- panther-4.1.3/panther/caching.py +125 -0
- panther-4.1.3/panther/cli/create_command.py +255 -0
- panther-4.1.3/panther/cli/monitor_command.py +97 -0
- {panther-3.3.1 → panther-4.1.3}/panther/cli/run_command.py +3 -1
- panther-4.1.3/panther/cli/template.py +231 -0
- {panther-3.3.1 → panther-4.1.3}/panther/cli/utils.py +43 -17
- panther-4.1.3/panther/configs.py +118 -0
- panther-4.1.3/panther/db/connections.py +145 -0
- panther-4.1.3/panther/db/cursor.py +43 -0
- panther-4.1.3/panther/db/models.py +84 -0
- panther-4.1.3/panther/db/queries/__init__.py +1 -0
- panther-4.1.3/panther/db/queries/base_queries.py +127 -0
- panther-4.1.3/panther/db/queries/mongodb_queries.py +143 -0
- panther-4.1.3/panther/db/queries/pantherdb_queries.py +110 -0
- panther-4.1.3/panther/db/queries/queries.py +395 -0
- {panther-3.3.1 → panther-4.1.3}/panther/db/utils.py +17 -23
- panther-4.1.3/panther/events.py +44 -0
- {panther-3.3.1 → panther-4.1.3}/panther/exceptions.py +26 -12
- {panther-3.3.1 → panther-4.1.3}/panther/file_handler.py +20 -3
- panther-4.1.3/panther/generics.py +191 -0
- {panther-3.3.1 → panther-4.1.3}/panther/logging.py +8 -3
- panther-4.1.3/panther/main.py +231 -0
- panther-4.1.3/panther/middlewares/base.py +30 -0
- {panther-3.3.1 → panther-4.1.3}/panther/monitoring.py +8 -5
- panther-4.1.3/panther/pagination.py +51 -0
- {panther-3.3.1 → panther-4.1.3}/panther/panel/apis.py +32 -5
- {panther-3.3.1 → panther-4.1.3}/panther/panel/urls.py +2 -1
- panther-4.1.3/panther/permissions.py +13 -0
- {panther-3.3.1 → panther-4.1.3}/panther/request.py +6 -13
- panther-4.1.3/panther/response.py +217 -0
- panther-4.1.3/panther/routings.py +189 -0
- panther-4.1.3/panther/serializer.py +231 -0
- {panther-3.3.1 → panther-4.1.3}/panther/test.py +81 -23
- panther-4.1.3/panther/utils.py +113 -0
- panther-4.1.3/panther/websocket.py +37 -0
- panther-4.1.3/panther.egg-info/PKG-INFO +204 -0
- {panther-3.3.1 → panther-4.1.3}/panther.egg-info/SOURCES.txt +16 -7
- panther-4.1.3/panther.egg-info/requires.txt +16 -0
- panther-4.1.3/pyproject.toml +11 -0
- {panther-3.3.1 → panther-4.1.3}/setup.py +11 -9
- panther-4.1.3/tests/test_authentication.py +170 -0
- {panther-3.3.1 → panther-4.1.3}/tests/test_background_tasks.py +32 -39
- {panther-3.3.1 → panther-4.1.3}/tests/test_caching.py +21 -20
- panther-4.1.3/tests/test_cli.py +147 -0
- panther-4.1.3/tests/test_database.py +524 -0
- panther-4.1.3/tests/test_events.py +123 -0
- panther-4.1.3/tests/test_generics.py +226 -0
- {panther-3.3.1 → panther-4.1.3}/tests/test_multipart.py +10 -10
- {panther-3.3.1 → panther-4.1.3}/tests/test_panel_apis.py +10 -7
- panther-4.1.3/tests/test_request.py +447 -0
- panther-4.1.3/tests/test_response.py +456 -0
- {panther-3.3.1 → panther-4.1.3}/tests/test_routing.py +227 -150
- panther-4.1.3/tests/test_run.py +67 -0
- panther-4.1.3/tests/test_serializer.py +399 -0
- panther-4.1.3/tests/test_throttling.py +74 -0
- {panther-3.3.1 → panther-4.1.3}/tests/test_utils.py +90 -49
- panther-4.1.3/tests/test_websockets.py +330 -0
- panther-3.3.1/PKG-INFO +0 -229
- panther-3.3.1/README.md +0 -200
- panther-3.3.1/panther/_load_configs.py +0 -246
- panther-3.3.1/panther/app.py +0 -224
- panther-3.3.1/panther/authentications.py +0 -128
- panther-3.3.1/panther/base_websocket.py +0 -152
- panther-3.3.1/panther/caching.py +0 -90
- panther-3.3.1/panther/cli/create_command.py +0 -106
- panther-3.3.1/panther/cli/monitor_command.py +0 -71
- panther-3.3.1/panther/cli/template.py +0 -137
- panther-3.3.1/panther/configs.py +0 -77
- panther-3.3.1/panther/db/connection.py +0 -73
- panther-3.3.1/panther/db/models.py +0 -47
- panther-3.3.1/panther/db/queries/__init__.py +0 -1
- panther-3.3.1/panther/db/queries/mongodb_queries.py +0 -89
- panther-3.3.1/panther/db/queries/pantherdb_queries.py +0 -81
- panther-3.3.1/panther/db/queries/queries.py +0 -269
- panther-3.3.1/panther/main.py +0 -305
- panther-3.3.1/panther/middlewares/base.py +0 -10
- panther-3.3.1/panther/middlewares/db.py +0 -18
- panther-3.3.1/panther/middlewares/redis.py +0 -43
- panther-3.3.1/panther/permissions.py +0 -13
- panther-3.3.1/panther/response.py +0 -104
- panther-3.3.1/panther/routings.py +0 -169
- panther-3.3.1/panther/utils.py +0 -59
- panther-3.3.1/panther/websocket.py +0 -62
- panther-3.3.1/panther.egg-info/PKG-INFO +0 -229
- panther-3.3.1/panther.egg-info/requires.txt +0 -14
- panther-3.3.1/pyproject.toml +0 -6
- panther-3.3.1/tests/test_cli.py +0 -86
- panther-3.3.1/tests/test_mongodb.py +0 -48
- panther-3.3.1/tests/test_pantherdb.py +0 -445
- panther-3.3.1/tests/test_request.py +0 -295
- panther-3.3.1/tests/test_run.py +0 -70
- panther-3.3.1/tests/test_simple_responses.py +0 -116
- {panther-3.3.1 → panther-4.1.3}/LICENSE +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/cli/__init__.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/cli/main.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/db/__init__.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/middlewares/__init__.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/panel/__init__.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/panel/utils.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/status.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther/throttling.py +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther.egg-info/dependency_links.txt +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther.egg-info/entry_points.txt +0 -0
- {panther-3.3.1 → panther-4.1.3}/panther.egg-info/top_level.txt +0 -0
- {panther-3.3.1 → panther-4.1.3}/setup.cfg +0 -0
- {panther-3.3.1 → panther-4.1.3}/tests/test_status.py +0 -0
panther-4.1.3/PKG-INFO
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: panther
|
3
|
+
Version: 4.1.3
|
4
|
+
Summary: Fast & Friendly, Web Framework For Building Async APIs
|
5
|
+
Home-page: https://github.com/alirn76/panther
|
6
|
+
Author: Ali RajabNezhad
|
7
|
+
Author-email: alirn76@yahoo.com
|
8
|
+
License: BSD-3-Clause license
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Requires-Python: >=3.10
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE
|
16
|
+
Requires-Dist: httptools~=0.6
|
17
|
+
Requires-Dist: pantherdb==2.1.0
|
18
|
+
Requires-Dist: pydantic~=2.6
|
19
|
+
Requires-Dist: rich~=13.7
|
20
|
+
Requires-Dist: uvicorn~=0.27
|
21
|
+
Requires-Dist: pytz~=2024.1
|
22
|
+
Provides-Extra: full
|
23
|
+
Requires-Dist: redis==5.0.1; extra == "full"
|
24
|
+
Requires-Dist: motor~=3.3; extra == "full"
|
25
|
+
Requires-Dist: bpython~=0.24; extra == "full"
|
26
|
+
Requires-Dist: ruff~=0.1.9; extra == "full"
|
27
|
+
Requires-Dist: python-jose~=3.3; extra == "full"
|
28
|
+
Requires-Dist: websockets~=12.0; extra == "full"
|
29
|
+
Requires-Dist: cryptography~=42.0; extra == "full"
|
30
|
+
Requires-Dist: watchfiles~=0.21.0; extra == "full"
|
31
|
+
|
32
|
+
|
33
|
+
[](https://pypi.org/project/panther/) [](https://pypi.org/project/panther/) [](https://codecov.io/github/AliRn76/panther) [](https://pepy.tech/project/panther) [](https://github.com/alirn76/panther/blob/main/LICENSE)
|
34
|
+
|
35
|
+
|
36
|
+
## Panther
|
37
|
+
<b>Is A Fast & Friendly Web Framework For Building Async APIs With Python 3.10+</b>
|
38
|
+
|
39
|
+
<p align="center">
|
40
|
+
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo-vertical.png" alt="logo" style="width: 450px">
|
41
|
+
</p>
|
42
|
+
|
43
|
+
**_📚 Full Documentation:_** [PantherPy.GitHub.io](https://pantherpy.github.io)
|
44
|
+
|
45
|
+
---
|
46
|
+
|
47
|
+
### Why Use Panther ?
|
48
|
+
- Include Simple **File-Base** Database ([PantherDB](https://pypi.org/project/pantherdb/))
|
49
|
+
- Built-in Document-oriented Databases **ODM** (**MongoDB**, PantherDB)
|
50
|
+
- Built-in **Websocket** Support
|
51
|
+
- Built-in API **Caching** System (In Memory, **Redis**)
|
52
|
+
- Built-in **Authentication** Classes
|
53
|
+
- Built-in **Permission** Classes
|
54
|
+
- Built-in Visual API **Monitoring** (In Terminal)
|
55
|
+
- Support Custom **Background Tasks**
|
56
|
+
- Support Custom **Middlewares**
|
57
|
+
- Support Custom **Throttling**
|
58
|
+
- Support **Function-Base** and **Class-Base** APIs
|
59
|
+
- It's One Of The **Fastest Python Framework**
|
60
|
+
---
|
61
|
+
|
62
|
+
### Supported by
|
63
|
+
<center>
|
64
|
+
<a href="https://drive.google.com/file/d/17xe1hicIiRF7SQ-clg9SETdc19SktCbV/view?usp=sharing">
|
65
|
+
<img alt="jetbrains" src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/jb_beam_50x50.png">
|
66
|
+
</a>
|
67
|
+
</center>
|
68
|
+
|
69
|
+
---
|
70
|
+
|
71
|
+
### Installation
|
72
|
+
```shell
|
73
|
+
$ pip install panther
|
74
|
+
```
|
75
|
+
|
76
|
+
### Usage
|
77
|
+
|
78
|
+
- #### Create Project
|
79
|
+
|
80
|
+
```shell
|
81
|
+
$ panther create
|
82
|
+
```
|
83
|
+
|
84
|
+
- #### Run Project
|
85
|
+
|
86
|
+
```shell
|
87
|
+
$ panther run --reload
|
88
|
+
```
|
89
|
+
_* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server_
|
90
|
+
|
91
|
+
- #### Monitoring Requests
|
92
|
+
|
93
|
+
```shell
|
94
|
+
$ panther monitor
|
95
|
+
```
|
96
|
+
|
97
|
+
- #### Python Shell
|
98
|
+
|
99
|
+
```shell
|
100
|
+
$ panther shell
|
101
|
+
```
|
102
|
+
|
103
|
+
---
|
104
|
+
|
105
|
+
### API Example
|
106
|
+
- Create `main.py`
|
107
|
+
|
108
|
+
```python
|
109
|
+
from datetime import datetime, timedelta
|
110
|
+
|
111
|
+
from panther import status, Panther
|
112
|
+
from panther.app import GenericAPI
|
113
|
+
from panther.response import Response
|
114
|
+
|
115
|
+
|
116
|
+
class FirstAPI(GenericAPI):
|
117
|
+
# Cache Response For 10 Seconds
|
118
|
+
cache = True
|
119
|
+
cache_exp_time = timedelta(seconds=10)
|
120
|
+
|
121
|
+
def get(self):
|
122
|
+
date_time = datetime.now().isoformat()
|
123
|
+
data = {'detail': f'Hello World | {date_time}'}
|
124
|
+
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
125
|
+
|
126
|
+
|
127
|
+
url_routing = {'': FirstAPI}
|
128
|
+
app = Panther(__name__, configs=__name__, urls=url_routing)
|
129
|
+
```
|
130
|
+
|
131
|
+
- Run the project:
|
132
|
+
- `$ panther run --reload`
|
133
|
+
|
134
|
+
- Checkout the [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
|
135
|
+
|
136
|
+
### WebSocket Echo Example
|
137
|
+
- Create `main.py`
|
138
|
+
|
139
|
+
```python
|
140
|
+
from panther import Panther
|
141
|
+
from panther.app import GenericAPI
|
142
|
+
from panther.response import HTMLResponse
|
143
|
+
from panther.websocket import GenericWebsocket
|
144
|
+
|
145
|
+
|
146
|
+
class FirstWebsocket(GenericWebsocket):
|
147
|
+
async def connect(self, **kwargs):
|
148
|
+
await self.accept()
|
149
|
+
|
150
|
+
async def receive(self, data: str | bytes):
|
151
|
+
await self.send(data)
|
152
|
+
|
153
|
+
|
154
|
+
class MainPage(GenericAPI):
|
155
|
+
def get(self):
|
156
|
+
template = """
|
157
|
+
<input type="text" id="messageInput">
|
158
|
+
<button id="sendButton">Send Message</button>
|
159
|
+
<ul id="messages"></ul>
|
160
|
+
<script>
|
161
|
+
var socket = new WebSocket('ws://127.0.0.1:8000/ws');
|
162
|
+
socket.addEventListener('message', function (event) {
|
163
|
+
var li = document.createElement('li');
|
164
|
+
document.getElementById('messages').appendChild(li).textContent = 'Server: ' + event.data;
|
165
|
+
});
|
166
|
+
function sendMessage() {
|
167
|
+
socket.send(document.getElementById('messageInput').value);
|
168
|
+
}
|
169
|
+
document.getElementById('sendButton').addEventListener('click', sendMessage);
|
170
|
+
</script>
|
171
|
+
"""
|
172
|
+
return HTMLResponse(template)
|
173
|
+
|
174
|
+
url_routing = {
|
175
|
+
'': MainPage,
|
176
|
+
'ws': FirstWebsocket,
|
177
|
+
}
|
178
|
+
app = Panther(__name__, configs=__name__, urls=url_routing)
|
179
|
+
|
180
|
+
```
|
181
|
+
|
182
|
+
- Run the project:
|
183
|
+
- `$ panther run --reload`
|
184
|
+
- Go to [http://127.0.0.1:8000/](http://127.0.0.1:8000/) and work with your `websocket`
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
> **Next Step: [First CRUD](https://pantherpy.github.io/function_first_crud)**
|
189
|
+
|
190
|
+
---
|
191
|
+
|
192
|
+
### How Panther Works!
|
193
|
+
|
194
|
+

|
195
|
+
|
196
|
+
---
|
197
|
+
|
198
|
+
### Roadmap
|
199
|
+
|
200
|
+

|
201
|
+
|
202
|
+
---
|
203
|
+
|
204
|
+
**If you find this project useful, please give it a star ⭐️.**
|
panther-4.1.3/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
|
2
|
+
[](https://pypi.org/project/panther/) [](https://pypi.org/project/panther/) [](https://codecov.io/github/AliRn76/panther) [](https://pepy.tech/project/panther) [](https://github.com/alirn76/panther/blob/main/LICENSE)
|
3
|
+
|
4
|
+
|
5
|
+
## Panther
|
6
|
+
<b>Is A Fast & Friendly Web Framework For Building Async APIs With Python 3.10+</b>
|
7
|
+
|
8
|
+
<p align="center">
|
9
|
+
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo-vertical.png" alt="logo" style="width: 450px">
|
10
|
+
</p>
|
11
|
+
|
12
|
+
**_📚 Full Documentation:_** [PantherPy.GitHub.io](https://pantherpy.github.io)
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
### Why Use Panther ?
|
17
|
+
- Include Simple **File-Base** Database ([PantherDB](https://pypi.org/project/pantherdb/))
|
18
|
+
- Built-in Document-oriented Databases **ODM** (**MongoDB**, PantherDB)
|
19
|
+
- Built-in **Websocket** Support
|
20
|
+
- Built-in API **Caching** System (In Memory, **Redis**)
|
21
|
+
- Built-in **Authentication** Classes
|
22
|
+
- Built-in **Permission** Classes
|
23
|
+
- Built-in Visual API **Monitoring** (In Terminal)
|
24
|
+
- Support Custom **Background Tasks**
|
25
|
+
- Support Custom **Middlewares**
|
26
|
+
- Support Custom **Throttling**
|
27
|
+
- Support **Function-Base** and **Class-Base** APIs
|
28
|
+
- It's One Of The **Fastest Python Framework**
|
29
|
+
---
|
30
|
+
|
31
|
+
### Supported by
|
32
|
+
<center>
|
33
|
+
<a href="https://drive.google.com/file/d/17xe1hicIiRF7SQ-clg9SETdc19SktCbV/view?usp=sharing">
|
34
|
+
<img alt="jetbrains" src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/jb_beam_50x50.png">
|
35
|
+
</a>
|
36
|
+
</center>
|
37
|
+
|
38
|
+
---
|
39
|
+
|
40
|
+
### Installation
|
41
|
+
```shell
|
42
|
+
$ pip install panther
|
43
|
+
```
|
44
|
+
|
45
|
+
### Usage
|
46
|
+
|
47
|
+
- #### Create Project
|
48
|
+
|
49
|
+
```shell
|
50
|
+
$ panther create
|
51
|
+
```
|
52
|
+
|
53
|
+
- #### Run Project
|
54
|
+
|
55
|
+
```shell
|
56
|
+
$ panther run --reload
|
57
|
+
```
|
58
|
+
_* Panther uses [Uvicorn](https://github.com/encode/uvicorn) as ASGI (Asynchronous Server Gateway Interface) but you can run the project with [Granian](https://pypi.org/project/granian/), [daphne](https://pypi.org/project/daphne/) or any ASGI server_
|
59
|
+
|
60
|
+
- #### Monitoring Requests
|
61
|
+
|
62
|
+
```shell
|
63
|
+
$ panther monitor
|
64
|
+
```
|
65
|
+
|
66
|
+
- #### Python Shell
|
67
|
+
|
68
|
+
```shell
|
69
|
+
$ panther shell
|
70
|
+
```
|
71
|
+
|
72
|
+
---
|
73
|
+
|
74
|
+
### API Example
|
75
|
+
- Create `main.py`
|
76
|
+
|
77
|
+
```python
|
78
|
+
from datetime import datetime, timedelta
|
79
|
+
|
80
|
+
from panther import status, Panther
|
81
|
+
from panther.app import GenericAPI
|
82
|
+
from panther.response import Response
|
83
|
+
|
84
|
+
|
85
|
+
class FirstAPI(GenericAPI):
|
86
|
+
# Cache Response For 10 Seconds
|
87
|
+
cache = True
|
88
|
+
cache_exp_time = timedelta(seconds=10)
|
89
|
+
|
90
|
+
def get(self):
|
91
|
+
date_time = datetime.now().isoformat()
|
92
|
+
data = {'detail': f'Hello World | {date_time}'}
|
93
|
+
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
94
|
+
|
95
|
+
|
96
|
+
url_routing = {'': FirstAPI}
|
97
|
+
app = Panther(__name__, configs=__name__, urls=url_routing)
|
98
|
+
```
|
99
|
+
|
100
|
+
- Run the project:
|
101
|
+
- `$ panther run --reload`
|
102
|
+
|
103
|
+
- Checkout the [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
|
104
|
+
|
105
|
+
### WebSocket Echo Example
|
106
|
+
- Create `main.py`
|
107
|
+
|
108
|
+
```python
|
109
|
+
from panther import Panther
|
110
|
+
from panther.app import GenericAPI
|
111
|
+
from panther.response import HTMLResponse
|
112
|
+
from panther.websocket import GenericWebsocket
|
113
|
+
|
114
|
+
|
115
|
+
class FirstWebsocket(GenericWebsocket):
|
116
|
+
async def connect(self, **kwargs):
|
117
|
+
await self.accept()
|
118
|
+
|
119
|
+
async def receive(self, data: str | bytes):
|
120
|
+
await self.send(data)
|
121
|
+
|
122
|
+
|
123
|
+
class MainPage(GenericAPI):
|
124
|
+
def get(self):
|
125
|
+
template = """
|
126
|
+
<input type="text" id="messageInput">
|
127
|
+
<button id="sendButton">Send Message</button>
|
128
|
+
<ul id="messages"></ul>
|
129
|
+
<script>
|
130
|
+
var socket = new WebSocket('ws://127.0.0.1:8000/ws');
|
131
|
+
socket.addEventListener('message', function (event) {
|
132
|
+
var li = document.createElement('li');
|
133
|
+
document.getElementById('messages').appendChild(li).textContent = 'Server: ' + event.data;
|
134
|
+
});
|
135
|
+
function sendMessage() {
|
136
|
+
socket.send(document.getElementById('messageInput').value);
|
137
|
+
}
|
138
|
+
document.getElementById('sendButton').addEventListener('click', sendMessage);
|
139
|
+
</script>
|
140
|
+
"""
|
141
|
+
return HTMLResponse(template)
|
142
|
+
|
143
|
+
url_routing = {
|
144
|
+
'': MainPage,
|
145
|
+
'ws': FirstWebsocket,
|
146
|
+
}
|
147
|
+
app = Panther(__name__, configs=__name__, urls=url_routing)
|
148
|
+
|
149
|
+
```
|
150
|
+
|
151
|
+
- Run the project:
|
152
|
+
- `$ panther run --reload`
|
153
|
+
- Go to [http://127.0.0.1:8000/](http://127.0.0.1:8000/) and work with your `websocket`
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
> **Next Step: [First CRUD](https://pantherpy.github.io/function_first_crud)**
|
158
|
+
|
159
|
+
---
|
160
|
+
|
161
|
+
### How Panther Works!
|
162
|
+
|
163
|
+

|
164
|
+
|
165
|
+
---
|
166
|
+
|
167
|
+
### Roadmap
|
168
|
+
|
169
|
+

|
170
|
+
|
171
|
+
---
|
172
|
+
|
173
|
+
**If you find this project useful, please give it a star ⭐️.**
|
@@ -0,0 +1,263 @@
|
|
1
|
+
import logging
|
2
|
+
import sys
|
3
|
+
from importlib import import_module
|
4
|
+
from multiprocessing import Manager
|
5
|
+
|
6
|
+
from panther._utils import import_class
|
7
|
+
from panther.background_tasks import background_tasks
|
8
|
+
from panther.base_websocket import WebsocketConnections
|
9
|
+
from panther.cli.utils import import_error
|
10
|
+
from panther.configs import JWTConfig, config
|
11
|
+
from panther.db.connections import redis
|
12
|
+
from panther.db.queries.mongodb_queries import BaseMongoDBQuery
|
13
|
+
from panther.db.queries.pantherdb_queries import BasePantherDBQuery
|
14
|
+
from panther.exceptions import PantherError
|
15
|
+
from panther.middlewares.base import WebsocketMiddleware, HTTPMiddleware
|
16
|
+
from panther.panel.urls import urls as panel_urls
|
17
|
+
from panther.routings import finalize_urls, flatten_urls
|
18
|
+
|
19
|
+
__all__ = (
|
20
|
+
'load_configs_module',
|
21
|
+
'load_redis',
|
22
|
+
'load_startup',
|
23
|
+
'load_shutdown',
|
24
|
+
'load_timezone',
|
25
|
+
'load_database',
|
26
|
+
'load_secret_key',
|
27
|
+
'load_monitoring',
|
28
|
+
'load_throttling',
|
29
|
+
'load_user_model',
|
30
|
+
'load_log_queries',
|
31
|
+
'load_middlewares',
|
32
|
+
'load_auto_reformat',
|
33
|
+
'load_background_tasks',
|
34
|
+
'load_default_cache_exp',
|
35
|
+
'load_authentication_class',
|
36
|
+
'load_urls',
|
37
|
+
'load_websocket_connections',
|
38
|
+
)
|
39
|
+
|
40
|
+
logger = logging.getLogger('panther')
|
41
|
+
|
42
|
+
|
43
|
+
def load_configs_module(module_name: str, /) -> dict:
|
44
|
+
"""Read the config file as dict"""
|
45
|
+
if module_name:
|
46
|
+
_module = sys.modules[module_name]
|
47
|
+
else:
|
48
|
+
try:
|
49
|
+
_module = import_module('core.configs')
|
50
|
+
except ModuleNotFoundError:
|
51
|
+
raise _exception_handler(field='core/configs.py', error='Not Found')
|
52
|
+
return _module.__dict__
|
53
|
+
|
54
|
+
|
55
|
+
def load_redis(_configs: dict, /) -> None:
|
56
|
+
if redis_config := _configs.get('REDIS'):
|
57
|
+
# Check redis module installation
|
58
|
+
try:
|
59
|
+
from redis.asyncio import Redis
|
60
|
+
except ImportError as e:
|
61
|
+
raise import_error(e, package='redis')
|
62
|
+
redis_class_path = redis_config.get('class', 'panther.db.connections.RedisConnection')
|
63
|
+
redis_class = import_class(redis_class_path)
|
64
|
+
# We have to create another dict then pop the 'class' else we can't pass the tests
|
65
|
+
args = redis_config.copy()
|
66
|
+
args.pop('class', None)
|
67
|
+
redis_class(**args, init=True)
|
68
|
+
|
69
|
+
|
70
|
+
def load_startup(_configs: dict, /) -> None:
|
71
|
+
if startup := _configs.get('STARTUP'):
|
72
|
+
config.STARTUP = import_class(startup)
|
73
|
+
|
74
|
+
|
75
|
+
def load_shutdown(_configs: dict, /) -> None:
|
76
|
+
if shutdown := _configs.get('SHUTDOWN'):
|
77
|
+
config.SHUTDOWN = import_class(shutdown)
|
78
|
+
|
79
|
+
|
80
|
+
def load_timezone(_configs: dict, /) -> None:
|
81
|
+
if timezone := _configs.get('TIMEZONE'):
|
82
|
+
config.TIMEZONE = timezone
|
83
|
+
|
84
|
+
|
85
|
+
def load_database(_configs: dict, /) -> None:
|
86
|
+
database_config = _configs.get('DATABASE', {})
|
87
|
+
if 'engine' in database_config:
|
88
|
+
if 'class' not in database_config['engine']:
|
89
|
+
raise _exception_handler(field='DATABASE', error=f'`engine["class"]` not found.')
|
90
|
+
|
91
|
+
engine_class_path = database_config['engine']['class']
|
92
|
+
engine_class = import_class(engine_class_path)
|
93
|
+
# We have to create another dict then pop the 'class' else we can't pass the tests
|
94
|
+
args = database_config['engine'].copy()
|
95
|
+
args.pop('class')
|
96
|
+
config.DATABASE = engine_class(**args)
|
97
|
+
|
98
|
+
if engine_class_path == 'panther.db.connections.PantherDBConnection':
|
99
|
+
config.QUERY_ENGINE = BasePantherDBQuery
|
100
|
+
elif engine_class_path == 'panther.db.connections.MongoDBConnection':
|
101
|
+
config.QUERY_ENGINE = BaseMongoDBQuery
|
102
|
+
|
103
|
+
if 'query' in database_config:
|
104
|
+
if config.QUERY_ENGINE:
|
105
|
+
logger.warning('`DATABASE.query` has already been filled.')
|
106
|
+
config.QUERY_ENGINE = import_class(database_config['query'])
|
107
|
+
|
108
|
+
|
109
|
+
def load_secret_key(_configs: dict, /) -> None:
|
110
|
+
if secret_key := _configs.get('SECRET_KEY'):
|
111
|
+
config.SECRET_KEY = secret_key.encode()
|
112
|
+
|
113
|
+
|
114
|
+
def load_monitoring(_configs: dict, /) -> None:
|
115
|
+
if _configs.get('MONITORING'):
|
116
|
+
config.MONITORING = True
|
117
|
+
|
118
|
+
|
119
|
+
def load_throttling(_configs: dict, /) -> None:
|
120
|
+
if throttling := _configs.get('THROTTLING'):
|
121
|
+
config.THROTTLING = throttling
|
122
|
+
|
123
|
+
|
124
|
+
def load_user_model(_configs: dict, /) -> None:
|
125
|
+
config.USER_MODEL = import_class(_configs.get('USER_MODEL', 'panther.db.models.BaseUser'))
|
126
|
+
config.MODELS.append(config.USER_MODEL)
|
127
|
+
|
128
|
+
|
129
|
+
def load_log_queries(_configs: dict, /) -> None:
|
130
|
+
if _configs.get('LOG_QUERIES'):
|
131
|
+
config.LOG_QUERIES = True
|
132
|
+
|
133
|
+
|
134
|
+
def load_middlewares(_configs: dict, /) -> None:
|
135
|
+
from panther.middlewares import BaseMiddleware
|
136
|
+
|
137
|
+
middlewares = {'http': [], 'ws': []}
|
138
|
+
|
139
|
+
# Collect Middlewares
|
140
|
+
for middleware in _configs.get('MIDDLEWARES') or []:
|
141
|
+
if not isinstance(middleware, list | tuple):
|
142
|
+
raise _exception_handler(field='MIDDLEWARES', error=f'{middleware} should have 2 part: (path, kwargs)')
|
143
|
+
|
144
|
+
if len(middleware) == 1:
|
145
|
+
path = middleware[0]
|
146
|
+
data = {}
|
147
|
+
|
148
|
+
elif len(middleware) > 2:
|
149
|
+
raise _exception_handler(field='MIDDLEWARES', error=f'{middleware} too many arguments')
|
150
|
+
|
151
|
+
else:
|
152
|
+
path, data = middleware
|
153
|
+
|
154
|
+
try:
|
155
|
+
middleware_class = import_class(path)
|
156
|
+
except (AttributeError, ModuleNotFoundError):
|
157
|
+
raise _exception_handler(field='MIDDLEWARES', error=f'{path} is not a valid middleware path')
|
158
|
+
|
159
|
+
if issubclass(middleware_class, BaseMiddleware) is False:
|
160
|
+
raise _exception_handler(field='MIDDLEWARES', error='is not a sub class of BaseMiddleware')
|
161
|
+
|
162
|
+
if middleware_class.__bases__[0] in (BaseMiddleware, HTTPMiddleware):
|
163
|
+
middlewares['http'].append((middleware_class, data))
|
164
|
+
|
165
|
+
if middleware_class.__bases__[0] in (BaseMiddleware, WebsocketMiddleware):
|
166
|
+
middlewares['ws'].append((middleware_class, data))
|
167
|
+
|
168
|
+
config.HTTP_MIDDLEWARES = middlewares['http']
|
169
|
+
config.WS_MIDDLEWARES = middlewares['ws']
|
170
|
+
|
171
|
+
|
172
|
+
def load_auto_reformat(_configs: dict, /) -> None:
|
173
|
+
if _configs.get('AUTO_REFORMAT'):
|
174
|
+
config.AUTO_REFORMAT = True
|
175
|
+
|
176
|
+
|
177
|
+
def load_background_tasks(_configs: dict, /) -> None:
|
178
|
+
if _configs.get('BACKGROUND_TASKS'):
|
179
|
+
config.BACKGROUND_TASKS = True
|
180
|
+
background_tasks.initialize()
|
181
|
+
|
182
|
+
|
183
|
+
def load_default_cache_exp(_configs: dict, /) -> None:
|
184
|
+
if default_cache_exp := _configs.get('DEFAULT_CACHE_EXP'):
|
185
|
+
config.DEFAULT_CACHE_EXP = default_cache_exp
|
186
|
+
|
187
|
+
|
188
|
+
def load_authentication_class(_configs: dict, /) -> None:
|
189
|
+
"""Should be after `load_secret_key()`"""
|
190
|
+
if authentication := _configs.get('AUTHENTICATION'):
|
191
|
+
config.AUTHENTICATION = import_class(authentication)
|
192
|
+
|
193
|
+
if ws_authentication := _configs.get('WS_AUTHENTICATION'):
|
194
|
+
config.WS_AUTHENTICATION = import_class(ws_authentication)
|
195
|
+
|
196
|
+
load_jwt_config(_configs)
|
197
|
+
|
198
|
+
|
199
|
+
def load_jwt_config(_configs: dict, /) -> None:
|
200
|
+
"""Only Collect JWT Config If Authentication Is JWTAuthentication"""
|
201
|
+
auth_is_jwt = (
|
202
|
+
getattr(config.AUTHENTICATION, '__name__', None) == 'JWTAuthentication' or
|
203
|
+
getattr(config.WS_AUTHENTICATION, '__name__', None) == 'QueryParamJWTAuthentication'
|
204
|
+
)
|
205
|
+
jwt = _configs.get('JWTConfig', {})
|
206
|
+
if auth_is_jwt or jwt:
|
207
|
+
if 'key' not in jwt:
|
208
|
+
if config.SECRET_KEY is None:
|
209
|
+
raise _exception_handler(field='JWTConfig', error='`JWTConfig.key` or `SECRET_KEY` is required.')
|
210
|
+
jwt['key'] = config.SECRET_KEY.decode()
|
211
|
+
config.JWT_CONFIG = JWTConfig(**jwt)
|
212
|
+
|
213
|
+
|
214
|
+
def load_urls(_configs: dict, /, urls: dict | None) -> None:
|
215
|
+
"""
|
216
|
+
Return tuple of all urls (as a flat dict) and (as a nested dict)
|
217
|
+
"""
|
218
|
+
if isinstance(urls, dict):
|
219
|
+
pass
|
220
|
+
|
221
|
+
elif (url_routing := _configs.get('URLs')) is None:
|
222
|
+
raise _exception_handler(field='URLs', error='required.')
|
223
|
+
|
224
|
+
elif isinstance(url_routing, dict):
|
225
|
+
error = (
|
226
|
+
"can't be 'dict', you may want to pass it's value directly to Panther(). " 'Example: Panther(..., urls=...)'
|
227
|
+
)
|
228
|
+
raise _exception_handler(field='URLs', error=error)
|
229
|
+
|
230
|
+
elif not isinstance(url_routing, str):
|
231
|
+
error = 'should be dotted string.'
|
232
|
+
raise _exception_handler(field='URLs', error=error)
|
233
|
+
|
234
|
+
else:
|
235
|
+
try:
|
236
|
+
urls = import_class(url_routing)
|
237
|
+
except ModuleNotFoundError as e:
|
238
|
+
raise _exception_handler(field='URLs', error=e)
|
239
|
+
|
240
|
+
if not isinstance(urls, dict):
|
241
|
+
raise _exception_handler(field='URLs', error='should point to a dict.')
|
242
|
+
|
243
|
+
config.FLAT_URLS = flatten_urls(urls)
|
244
|
+
config.URLS = finalize_urls(config.FLAT_URLS)
|
245
|
+
config.URLS['_panel'] = finalize_urls(flatten_urls(panel_urls))
|
246
|
+
|
247
|
+
|
248
|
+
def load_websocket_connections():
|
249
|
+
"""Should be after `load_redis()`"""
|
250
|
+
if config.HAS_WS:
|
251
|
+
# Check `websockets`
|
252
|
+
try:
|
253
|
+
import websockets
|
254
|
+
except ImportError as e:
|
255
|
+
raise import_error(e, package='websockets')
|
256
|
+
|
257
|
+
# Use the redis pubsub if `redis.is_connected`, else use the `multiprocessing.Manager`
|
258
|
+
pubsub_connection = redis.create_connection_for_websocket() if redis.is_connected else Manager()
|
259
|
+
config.WEBSOCKET_CONNECTIONS = WebsocketConnections(pubsub_connection=pubsub_connection)
|
260
|
+
|
261
|
+
|
262
|
+
def _exception_handler(field: str, error: str | Exception) -> PantherError:
|
263
|
+
return PantherError(f"Invalid '{field}': {error}")
|