panther 3.8.2__py3-none-any.whl → 4.0.0__py3-none-any.whl
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/__init__.py +1 -1
- panther/_load_configs.py +168 -171
- panther/_utils.py +26 -49
- panther/app.py +85 -105
- panther/authentications.py +86 -55
- panther/background_tasks.py +25 -14
- panther/base_request.py +38 -14
- panther/base_websocket.py +172 -94
- panther/caching.py +60 -25
- panther/cli/create_command.py +20 -10
- panther/cli/monitor_command.py +63 -37
- panther/cli/template.py +40 -20
- panther/cli/utils.py +32 -18
- panther/configs.py +65 -58
- panther/db/connections.py +139 -0
- panther/db/cursor.py +43 -0
- panther/db/models.py +64 -29
- panther/db/queries/__init__.py +1 -1
- panther/db/queries/base_queries.py +127 -0
- panther/db/queries/mongodb_queries.py +77 -38
- panther/db/queries/pantherdb_queries.py +59 -30
- panther/db/queries/queries.py +232 -117
- panther/db/utils.py +17 -18
- panther/events.py +44 -0
- panther/exceptions.py +26 -12
- panther/file_handler.py +2 -2
- panther/generics.py +163 -0
- panther/logging.py +7 -2
- panther/main.py +111 -188
- panther/middlewares/base.py +3 -0
- panther/monitoring.py +8 -5
- panther/pagination.py +48 -0
- panther/panel/apis.py +32 -5
- panther/panel/urls.py +2 -1
- panther/permissions.py +3 -3
- panther/request.py +6 -13
- panther/response.py +114 -34
- panther/routings.py +83 -66
- panther/serializer.py +214 -33
- panther/test.py +31 -21
- panther/utils.py +28 -16
- panther/websocket.py +7 -4
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/METADATA +93 -71
- panther-4.0.0.dist-info/RECORD +57 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/WHEEL +1 -1
- panther/db/connection.py +0 -92
- panther/middlewares/db.py +0 -18
- panther/middlewares/redis.py +0 -47
- panther-3.8.2.dist-info/RECORD +0 -54
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/LICENSE +0 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/entry_points.txt +0 -0
- {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/top_level.txt +0 -0
panther/test.py
CHANGED
@@ -4,7 +4,7 @@ from typing import Literal
|
|
4
4
|
|
5
5
|
import orjson as json
|
6
6
|
|
7
|
-
from panther.response import Response
|
7
|
+
from panther.response import Response, HTMLResponse, PlainTextResponse, StreamingResponse
|
8
8
|
|
9
9
|
__all__ = ('APIClient', 'WebsocketClient')
|
10
10
|
|
@@ -12,12 +12,13 @@ __all__ = ('APIClient', 'WebsocketClient')
|
|
12
12
|
class RequestClient:
|
13
13
|
def __init__(self, app: Callable):
|
14
14
|
self.app = app
|
15
|
+
self.response = b''
|
15
16
|
|
16
17
|
async def send(self, data: dict):
|
17
18
|
if data['type'] == 'http.response.start':
|
18
19
|
self.header = data
|
19
20
|
else:
|
20
|
-
self.response
|
21
|
+
self.response += data['body']
|
21
22
|
|
22
23
|
async def receive(self):
|
23
24
|
return {
|
@@ -54,18 +55,29 @@ class RequestClient:
|
|
54
55
|
receive=self.receive,
|
55
56
|
send=self.send,
|
56
57
|
)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
response_headers = {key.decode(): value.decode() for key, value in self.header['headers']}
|
59
|
+
if response_headers['Content-Type'] == 'text/html; charset=utf-8':
|
60
|
+
data = self.response.decode()
|
61
|
+
return HTMLResponse(data=data, status_code=self.header['status'], headers=response_headers)
|
62
|
+
|
63
|
+
elif response_headers['Content-Type'] == 'text/plain; charset=utf-8':
|
64
|
+
data = self.response.decode()
|
65
|
+
return PlainTextResponse(data=data, status_code=self.header['status'], headers=response_headers)
|
66
|
+
|
67
|
+
elif response_headers['Content-Type'] == 'application/octet-stream':
|
68
|
+
data = self.response.decode()
|
69
|
+
return PlainTextResponse(data=data, status_code=self.header['status'], headers=response_headers)
|
70
|
+
|
71
|
+
else:
|
72
|
+
data = json.loads(self.response or b'null')
|
73
|
+
return Response(data=data, status_code=self.header['status'], headers=response_headers)
|
62
74
|
|
63
75
|
|
64
76
|
class APIClient:
|
65
77
|
def __init__(self, app: Callable):
|
66
78
|
self._app = app
|
67
79
|
|
68
|
-
def _send_request(
|
80
|
+
async def _send_request(
|
69
81
|
self,
|
70
82
|
path: str,
|
71
83
|
method: Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
@@ -74,23 +86,21 @@ class APIClient:
|
|
74
86
|
query_params: dict,
|
75
87
|
) -> Response:
|
76
88
|
request_client = RequestClient(app=self._app)
|
77
|
-
return
|
78
|
-
request_client.request(
|
89
|
+
return await request_client.request(
|
79
90
|
path=path,
|
80
91
|
method=method,
|
81
92
|
payload=payload,
|
82
93
|
headers=headers,
|
83
94
|
query_params=query_params or {},
|
84
95
|
)
|
85
|
-
)
|
86
96
|
|
87
|
-
def get(
|
97
|
+
async def get(
|
88
98
|
self,
|
89
99
|
path: str,
|
90
100
|
headers: dict | None = None,
|
91
101
|
query_params: dict | None = None,
|
92
102
|
) -> Response:
|
93
|
-
return self._send_request(
|
103
|
+
return await self._send_request(
|
94
104
|
path=path,
|
95
105
|
method='GET',
|
96
106
|
payload=None,
|
@@ -98,7 +108,7 @@ class APIClient:
|
|
98
108
|
query_params=query_params or {},
|
99
109
|
)
|
100
110
|
|
101
|
-
def post(
|
111
|
+
async def post(
|
102
112
|
self,
|
103
113
|
path: str,
|
104
114
|
payload: bytes | dict | None = None,
|
@@ -107,7 +117,7 @@ class APIClient:
|
|
107
117
|
content_type: str = 'application/json',
|
108
118
|
) -> Response:
|
109
119
|
headers = {'content-type': content_type} | (headers or {})
|
110
|
-
return self._send_request(
|
120
|
+
return await self._send_request(
|
111
121
|
path=path,
|
112
122
|
method='POST',
|
113
123
|
payload=payload,
|
@@ -115,7 +125,7 @@ class APIClient:
|
|
115
125
|
query_params=query_params or {},
|
116
126
|
)
|
117
127
|
|
118
|
-
def put(
|
128
|
+
async def put(
|
119
129
|
self,
|
120
130
|
path: str,
|
121
131
|
payload: bytes | dict | None = None,
|
@@ -124,7 +134,7 @@ class APIClient:
|
|
124
134
|
content_type: Literal['application/json', 'multipart/form-data'] = 'application/json',
|
125
135
|
) -> Response:
|
126
136
|
headers = {'content-type': content_type} | (headers or {})
|
127
|
-
return self._send_request(
|
137
|
+
return await self._send_request(
|
128
138
|
path=path,
|
129
139
|
method='PUT',
|
130
140
|
payload=payload,
|
@@ -132,7 +142,7 @@ class APIClient:
|
|
132
142
|
query_params=query_params or {},
|
133
143
|
)
|
134
144
|
|
135
|
-
def patch(
|
145
|
+
async def patch(
|
136
146
|
self,
|
137
147
|
path: str,
|
138
148
|
payload: bytes | dict | None = None,
|
@@ -141,7 +151,7 @@ class APIClient:
|
|
141
151
|
content_type: Literal['application/json', 'multipart/form-data'] = 'application/json',
|
142
152
|
) -> Response:
|
143
153
|
headers = {'content-type': content_type} | (headers or {})
|
144
|
-
return self._send_request(
|
154
|
+
return await self._send_request(
|
145
155
|
path=path,
|
146
156
|
method='PATCH',
|
147
157
|
payload=payload,
|
@@ -149,13 +159,13 @@ class APIClient:
|
|
149
159
|
query_params=query_params or {},
|
150
160
|
)
|
151
161
|
|
152
|
-
def delete(
|
162
|
+
async def delete(
|
153
163
|
self,
|
154
164
|
path: str,
|
155
165
|
headers: dict | None = None,
|
156
166
|
query_params: dict | None = None,
|
157
167
|
) -> Response:
|
158
|
-
return self._send_request(
|
168
|
+
return await self._send_request(
|
159
169
|
path=path,
|
160
170
|
method='DELETE',
|
161
171
|
payload=None,
|
panther/utils.py
CHANGED
@@ -2,10 +2,15 @@ import base64
|
|
2
2
|
import hashlib
|
3
3
|
import logging
|
4
4
|
import os
|
5
|
-
|
5
|
+
import secrets
|
6
|
+
from datetime import datetime, timedelta, timezone
|
6
7
|
from pathlib import Path
|
7
8
|
from typing import ClassVar
|
8
9
|
|
10
|
+
import pytz
|
11
|
+
|
12
|
+
from panther.configs import config
|
13
|
+
|
9
14
|
logger = logging.getLogger('panther')
|
10
15
|
|
11
16
|
URANDOM_SIZE = 16
|
@@ -24,8 +29,7 @@ def load_env(env_file: str | Path, /) -> dict[str, str]:
|
|
24
29
|
variables = {}
|
25
30
|
|
26
31
|
if env_file is None or not Path(env_file).is_file():
|
27
|
-
|
28
|
-
return variables
|
32
|
+
raise ValueError(f'"{env_file}" is not a file.') from None
|
29
33
|
|
30
34
|
with open(env_file) as file:
|
31
35
|
for line in file.readlines():
|
@@ -79,23 +83,31 @@ def scrypt(password: str, salt: bytes, digest: bool = False) -> str | bytes:
|
|
79
83
|
dklen=dk_len
|
80
84
|
)
|
81
85
|
if digest:
|
82
|
-
|
83
86
|
return hashlib.md5(derived_key).hexdigest()
|
84
|
-
|
85
|
-
|
87
|
+
return derived_key
|
88
|
+
|
86
89
|
|
90
|
+
class ULID:
|
91
|
+
"""https://github.com/ulid/spec"""
|
87
92
|
|
88
|
-
|
89
|
-
salt = os.urandom(URANDOM_SIZE)
|
90
|
-
derived_key = scrypt(password=password, salt=salt, digest=True)
|
93
|
+
crockford_base32_characters = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
91
94
|
|
92
|
-
|
95
|
+
@classmethod
|
96
|
+
def new(cls):
|
97
|
+
current_timestamp = int(datetime.now(timezone.utc).timestamp() * 1000)
|
98
|
+
epoch_bits = '{0:050b}'.format(current_timestamp)
|
99
|
+
random_bits = '{0:080b}'.format(secrets.randbits(80))
|
100
|
+
bits = epoch_bits + random_bits
|
101
|
+
return cls._generate(bits)
|
93
102
|
|
103
|
+
@classmethod
|
104
|
+
def _generate(cls, bits: str) -> str:
|
105
|
+
return ''.join(
|
106
|
+
cls.crockford_base32_characters[int(bits[i: i + 5], base=2)]
|
107
|
+
for i in range(0, 130, 5)
|
108
|
+
)
|
94
109
|
|
95
|
-
def check_password(stored_password: str, new_password: str) -> bool:
|
96
|
-
size = URANDOM_SIZE * 2
|
97
|
-
salt = stored_password[:size]
|
98
|
-
stored_hash = stored_password[size:]
|
99
|
-
derived_key = scrypt(password=new_password, salt=bytes.fromhex(salt), digest=True)
|
100
110
|
|
101
|
-
|
111
|
+
def timezone_now():
|
112
|
+
tz = pytz.timezone(config.TIMEZONE)
|
113
|
+
return datetime.now(tz=tz)
|
panther/websocket.py
CHANGED
@@ -6,9 +6,12 @@ from panther.configs import config
|
|
6
6
|
|
7
7
|
|
8
8
|
class GenericWebsocket(Websocket):
|
9
|
+
auth: bool = False
|
10
|
+
permissions: list = []
|
11
|
+
|
9
12
|
async def connect(self, **kwargs):
|
10
13
|
"""
|
11
|
-
Check your conditions then `accept()` the connection
|
14
|
+
Check your conditions then `accept()` or `close()` the connection
|
12
15
|
"""
|
13
16
|
|
14
17
|
async def receive(self, data: str | bytes):
|
@@ -19,16 +22,16 @@ class GenericWebsocket(Websocket):
|
|
19
22
|
|
20
23
|
async def send(self, data: any = None):
|
21
24
|
"""
|
22
|
-
|
25
|
+
Send message to the client,
|
23
26
|
You may want to override it with your custom scenario. (not recommended)
|
24
27
|
"""
|
25
28
|
return await super().send(data=data)
|
26
29
|
|
27
30
|
|
28
31
|
async def send_message_to_websocket(connection_id: str, data: any):
|
29
|
-
config.
|
32
|
+
await config.WEBSOCKET_CONNECTIONS.publish(connection_id=connection_id, action='send', data=data)
|
30
33
|
|
31
34
|
|
32
35
|
async def close_websocket_connection(connection_id: str, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ''):
|
33
36
|
data = {'code': code, 'reason': reason}
|
34
|
-
config.
|
37
|
+
await config.WEBSOCKET_CONNECTIONS.publish(connection_id=connection_id, action='close', data=data)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version:
|
3
|
+
Version: 4.0.0
|
4
4
|
Summary: Fast & Friendly, Web Framework For Building Async APIs
|
5
5
|
Home-page: https://github.com/alirn76/panther
|
6
6
|
Author: Ali RajabNezhad
|
@@ -13,21 +13,21 @@ Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Requires-Python: >=3.10
|
14
14
|
Description-Content-Type: text/markdown
|
15
15
|
License-File: LICENSE
|
16
|
-
Requires-Dist: bson ~=0.5
|
17
16
|
Requires-Dist: httptools ~=0.6
|
18
|
-
Requires-Dist: pantherdb ==1.
|
19
|
-
Requires-Dist: pydantic ~=2.
|
20
|
-
Requires-Dist: rich ~=13.
|
21
|
-
Requires-Dist: uvicorn ~=0.
|
22
|
-
Requires-Dist:
|
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
|
23
22
|
Provides-Extra: full
|
24
23
|
Requires-Dist: redis ==5.0.1 ; extra == 'full'
|
25
|
-
Requires-Dist:
|
24
|
+
Requires-Dist: motor ~=3.3 ; extra == 'full'
|
26
25
|
Requires-Dist: bpython ~=0.24 ; extra == 'full'
|
27
26
|
Requires-Dist: ruff ~=0.1.9 ; extra == 'full'
|
28
27
|
Requires-Dist: python-jose ~=3.3 ; extra == 'full'
|
29
28
|
Requires-Dist: websockets ~=12.0 ; extra == 'full'
|
30
|
-
Requires-Dist: cryptography ~=
|
29
|
+
Requires-Dist: cryptography ~=42.0 ; extra == 'full'
|
30
|
+
Requires-Dist: watchfiles ~=0.21.0 ; extra == 'full'
|
31
31
|
|
32
32
|
|
33
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)
|
@@ -45,14 +45,18 @@ Requires-Dist: cryptography ~=41.0 ; extra == 'full'
|
|
45
45
|
---
|
46
46
|
|
47
47
|
### Why Use Panther ?
|
48
|
-
-
|
49
|
-
- Built-in
|
50
|
-
-
|
51
|
-
- Built-in
|
52
|
-
- Built-in
|
53
|
-
-
|
54
|
-
-
|
55
|
-
-
|
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 Frameworks**
|
56
60
|
---
|
57
61
|
|
58
62
|
### Supported by
|
@@ -84,111 +88,129 @@ Requires-Dist: cryptography ~=41.0 ; extra == 'full'
|
|
84
88
|
---
|
85
89
|
|
86
90
|
### Installation
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
</details>
|
92
|
-
|
93
|
-
- <details>
|
94
|
-
<summary>2. Active The Environment</summary>
|
95
|
-
* Linux & Mac
|
96
|
-
<pre>$ source .venv/bin/activate</pre>
|
97
|
-
* Windows
|
98
|
-
<pre>$ .\.venv\Scripts\activate</pre>
|
99
|
-
|
100
|
-
</details>
|
101
|
-
|
102
|
-
- <details open>
|
103
|
-
<summary>3. <b>Install Panther</b></summary>
|
104
|
-
- ⬇ Normal Installation
|
105
|
-
<pre><b>$ pip install panther</b></pre>
|
106
|
-
- ⬇ Include full requirements (MongoDB, JWTAuth, Ruff, Redis, bpython)
|
107
|
-
<pre>$ pip install panther[full]</pre>
|
108
|
-
</details>
|
109
|
-
|
110
|
-
---
|
91
|
+
```shell
|
92
|
+
$ pip install panther
|
93
|
+
```
|
111
94
|
|
112
95
|
### Usage
|
113
96
|
|
114
97
|
- #### Create Project
|
115
98
|
|
116
|
-
```
|
99
|
+
```shell
|
117
100
|
$ panther create
|
118
101
|
```
|
119
102
|
|
120
103
|
- #### Run Project
|
121
104
|
|
122
|
-
```
|
105
|
+
```shell
|
123
106
|
$ panther run --reload
|
124
107
|
```
|
125
108
|
_* 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 too_
|
126
109
|
|
127
110
|
- #### Monitoring Requests
|
128
111
|
|
129
|
-
```
|
112
|
+
```shell
|
130
113
|
$ panther monitor
|
131
114
|
```
|
132
115
|
|
133
116
|
- #### Python Shell
|
134
117
|
|
135
|
-
```
|
118
|
+
```shell
|
136
119
|
$ panther shell
|
137
120
|
```
|
138
121
|
|
139
122
|
---
|
140
123
|
|
141
|
-
###
|
124
|
+
### API Example
|
142
125
|
- Create `main.py`
|
143
126
|
|
144
127
|
```python
|
145
128
|
from datetime import datetime, timedelta
|
146
129
|
|
147
|
-
from panther import
|
148
|
-
from panther.app import
|
149
|
-
from panther.request import Request
|
130
|
+
from panther import status, Panther
|
131
|
+
from panther.app import GenericAPI
|
150
132
|
from panther.response import Response
|
151
|
-
from panther.throttling import Throttling
|
152
133
|
|
153
|
-
InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
|
154
134
|
|
135
|
+
class FirstAPI(GenericAPI):
|
136
|
+
# Cache Response For 10 Seconds
|
137
|
+
cache = True
|
138
|
+
cache_exp_time = timedelta(seconds=10)
|
139
|
+
|
140
|
+
def get(self):
|
141
|
+
date_time = datetime.now().isoformat()
|
142
|
+
data = {'detail': f'Hello World | {date_time}'}
|
143
|
+
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
155
144
|
|
156
|
-
|
157
|
-
|
158
|
-
|
145
|
+
|
146
|
+
url_routing = {'': FirstAPI}
|
147
|
+
app = Panther(__name__, configs=__name__, urls=url_routing)
|
148
|
+
```
|
149
|
+
|
150
|
+
- Run the project:
|
151
|
+
- `$ panther run --reload`
|
152
|
+
|
153
|
+
- Checkout the [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
|
154
|
+
|
155
|
+
### WebSocket Echo Example
|
156
|
+
- Create `main.py`
|
157
|
+
|
158
|
+
```python
|
159
|
+
from panther import Panther
|
160
|
+
from panther.app import GenericAPI
|
161
|
+
from panther.response import HTMLResponse
|
162
|
+
from panther.websocket import GenericWebsocket
|
159
163
|
|
160
164
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
'panther_version': version(),
|
165
|
-
'datetime_now': datetime.now().isoformat(),
|
166
|
-
'user_agent': request.headers.user_agent
|
167
|
-
}
|
168
|
-
return Response(data=data, status_code=status.HTTP_202_ACCEPTED)
|
165
|
+
class FirstWebsocket(GenericWebsocket):
|
166
|
+
async def connect(self, **kwargs):
|
167
|
+
await self.accept()
|
169
168
|
|
169
|
+
async def receive(self, data: str | bytes):
|
170
|
+
await self.send(data)
|
171
|
+
|
172
|
+
|
173
|
+
class MainPage(GenericAPI):
|
174
|
+
def get(self):
|
175
|
+
template = """
|
176
|
+
<input type="text" id="messageInput">
|
177
|
+
<button id="sendButton">Send Message</button>
|
178
|
+
<ul id="messages"></ul>
|
179
|
+
<script>
|
180
|
+
var socket = new WebSocket('ws://127.0.0.1:8000/ws');
|
181
|
+
socket.addEventListener('message', function (event) {
|
182
|
+
var li = document.createElement('li');
|
183
|
+
document.getElementById('messages').appendChild(li).textContent = 'Server: ' + event.data;
|
184
|
+
});
|
185
|
+
function sendMessage() {
|
186
|
+
socket.send(document.getElementById('messageInput').value);
|
187
|
+
}
|
188
|
+
document.getElementById('sendButton').addEventListener('click', sendMessage);
|
189
|
+
</script>
|
190
|
+
"""
|
191
|
+
return HTMLResponse(template)
|
170
192
|
|
171
193
|
url_routing = {
|
172
|
-
'':
|
173
|
-
'
|
194
|
+
'': MainPage,
|
195
|
+
'ws': FirstWebsocket,
|
174
196
|
}
|
175
|
-
|
176
197
|
app = Panther(__name__, configs=__name__, urls=url_routing)
|
198
|
+
|
177
199
|
```
|
178
200
|
|
179
201
|
- Run the project:
|
180
202
|
- `$ panther run --reload`
|
181
|
-
|
182
|
-
|
183
|
-
- Now you can see these two urls:</b>
|
184
|
-
- [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
|
185
|
-
- [http://127.0.0.1:8000/info/](http://127.0.0.1:8000/info/)
|
203
|
+
- Go to [http://127.0.0.1:8000/](http://127.0.0.1:8000/) and work with your `websocket`
|
186
204
|
|
187
205
|
|
188
206
|
|
189
207
|
> **Next Step: [First CRUD](https://pantherpy.github.io/function_first_crud)**
|
190
208
|
|
191
|
-
|
209
|
+
---
|
210
|
+
|
211
|
+
### How Panther Works!
|
212
|
+
|
213
|
+

|
192
214
|
|
193
215
|
---
|
194
216
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
panther/__init__.py,sha256=0iWjkkDL_Hzzizq4nbcZTNPUIHXJSl9kdGprGpK2cNs,110
|
2
|
+
panther/_load_configs.py,sha256=AVkoixkUFkBQiTmrLrwCmg0eiPW2U_Uw2EGNEGQRfnI,9281
|
3
|
+
panther/_utils.py,sha256=xeVR0yHvczhXv2XXrpoa6SHpGTDTFxNxiemXTdbsqjM,4279
|
4
|
+
panther/app.py,sha256=X4vmpj5QuEt10iSVso89hGfyw75vemWtHPtdrXX4nD4,7276
|
5
|
+
panther/authentications.py,sha256=gf7BVyQ8vXKhiumJAtD0aAK7uIHWx_snbOKYAKrYuVw,5677
|
6
|
+
panther/background_tasks.py,sha256=HBYubDIiO_673cl_5fqCUP9zzimzRgRkDSkag9Msnbs,7656
|
7
|
+
panther/base_request.py,sha256=w48-1gQzJi5m9AUZzspdugffUS73lZ8Lw0N9AND_XDM,4064
|
8
|
+
panther/base_websocket.py,sha256=svVAT06YZXkiSV15lO509l_S3o4Pm--sxtbnXCSGszo,10585
|
9
|
+
panther/caching.py,sha256=ltuJYdjNiAaKIs3jpO5EBpL8Y6CF1vAIQqh8J_Np10g,4098
|
10
|
+
panther/configs.py,sha256=EaLApT6nYcguBoNXBG_8n6DU6HTNxsulI2943j8UAkE,3174
|
11
|
+
panther/events.py,sha256=bxDqrfiNNBlvD03vEk2LDK4xbMzTMFVcgAjx2ein7mI,1158
|
12
|
+
panther/exceptions.py,sha256=7rHdJIES2__kqOStIqbHl3Uxask2lzKgLQlkZvvDwFA,1591
|
13
|
+
panther/file_handler.py,sha256=XnomEigCUYOaXjkH4kD1kzpUbL2i9lLnR5kerruF6BA,846
|
14
|
+
panther/generics.py,sha256=eR97UgsH2WdqWTR5aw94niIJwIAlhgAz2wNVz_N3vIk,6311
|
15
|
+
panther/logging.py,sha256=t0nQXsSIwIxShqFnjRGp6lhO4Ybf1SnwJraDSTqMHFM,2211
|
16
|
+
panther/main.py,sha256=9LScJXNsj_LEeX1OrsHKWqKDFNA8UKn-Fr9rAXZN-mQ,9296
|
17
|
+
panther/monitoring.py,sha256=y1F3c8FJlnmooM-m1nSyOTa9eWq0v1nHnmw9zz-4Kls,1314
|
18
|
+
panther/pagination.py,sha256=efpsWMgLBaTWXhnhMAf6fyIrGTmVOFbmHpX03GgEJh0,1574
|
19
|
+
panther/permissions.py,sha256=9-J5vzvEKa_PITwEVQbZZv8PG2FOu05YBlD5yMrKcfc,348
|
20
|
+
panther/request.py,sha256=F9ZiAWSse7_6moAzqdoFInUN4zTKlzijh9AdU9w3Jfw,1673
|
21
|
+
panther/response.py,sha256=OvuxKueM5FJ7gg9icsR5NjVxkU9_4dsgShcOJFMcdaY,6458
|
22
|
+
panther/routings.py,sha256=1eqbjubLnUUEQRlz8mIF464ImvCMjyasiekHBtxEQoQ,6218
|
23
|
+
panther/serializer.py,sha256=dvPbDf6qICMuhTtX1ZXW0G5_BCHcLwughGOb1w3umXo,9002
|
24
|
+
panther/status.py,sha256=Gc_PnYrHfInTsZpGbqiCfDB-py1C7Rh8KMdb6Lq9Exs,3346
|
25
|
+
panther/test.py,sha256=RsQtP5IURLWR__BihOjruWoX3NscmGDqDqj1CfAb3bI,7037
|
26
|
+
panther/throttling.py,sha256=mVa_mGv6w_Ad7LLtV4eG5QpDwwNsk4QjFFi0mIHQBnE,231
|
27
|
+
panther/utils.py,sha256=Iq5q1suIgBBQGO5UctwR4HXs8E6zclXNh5lYc8k1Vjg,3409
|
28
|
+
panther/websocket.py,sha256=5WLw--Oa-6kGYbeRvO79hjbd0ARFcTTF40-hO_bdjmQ,1206
|
29
|
+
panther/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
panther/cli/create_command.py,sha256=cVhz0VQOvEbpufFsevH9T1rYZ8T_Wsa89cpTiIVKTC0,10294
|
31
|
+
panther/cli/main.py,sha256=pCqnOTazgMhTvFHTugutIsiFXueU5kx2VmGngwAl54Q,1679
|
32
|
+
panther/cli/monitor_command.py,sha256=aK70JyQz7-NXBYR4Ed4YQwtR55ypks9vaTK_h7Ck5kE,3105
|
33
|
+
panther/cli/run_command.py,sha256=X_T9jP_Z8X_fm9S4LSoR6ARsPp4rCTMi1E5c7QWREjM,2120
|
34
|
+
panther/cli/template.py,sha256=rsyKOQ0l2v3kdwmLiZxt5ecIhDzmFprCCv0uVAv7eQI,5319
|
35
|
+
panther/cli/utils.py,sha256=Jd4YQ9H6lapVktl7ZmiORt30WVmKI85xcwsY-fMRq3c,5289
|
36
|
+
panther/db/__init__.py,sha256=w9lEL0vRqb18Qx_iUJipUR_fi5GQ5uVX0DWycx14x08,50
|
37
|
+
panther/db/connections.py,sha256=P2mNO_WHJmQxwCL3VgZ-5AN97yx_cMHF2MevZyxlZDc,3959
|
38
|
+
panther/db/cursor.py,sha256=jJ6bhz_Zljt3-AoeVdi563e2q3MSDJPP33WVbQk-goE,1287
|
39
|
+
panther/db/models.py,sha256=BjBOdTyByDWs3S2V0leERd_47ZhoTbH306TIp-0Wgo8,2570
|
40
|
+
panther/db/utils.py,sha256=Uxh7UebkBv4thMCfooYW1pkuorFgocsbnBZJi-hHtdY,1582
|
41
|
+
panther/db/queries/__init__.py,sha256=uF4gvBjLBJ-Yl3WLqoZEVNtHCVhFRKW3_Vi44pJxDNI,45
|
42
|
+
panther/db/queries/base_queries.py,sha256=8HhdlsSW-lgz3-IrZYfOtHNC3TBWbCNErDR4XE718AY,3764
|
43
|
+
panther/db/queries/mongodb_queries.py,sha256=GZkkmfFTSjLVNTH3jSl3VM7MVHnXE0Fg6ob77yEYRPQ,5034
|
44
|
+
panther/db/queries/pantherdb_queries.py,sha256=_dA4gXk1IA5jzIy6_6o1zgdZeeka6SPihvQeSkj7h68,4481
|
45
|
+
panther/db/queries/queries.py,sha256=IK7NmSodftRbmWXrW4ddxyOHWU4L3WyuMbY0uG1WeGE,11462
|
46
|
+
panther/middlewares/__init__.py,sha256=ydo0bSadGqa2v7Xy1oCTkF2uXrImedXjiyx2vPTwPhE,66
|
47
|
+
panther/middlewares/base.py,sha256=tX0MBvDBkbsAB_DilRIYvcggSAqCzazRTb9MegZNdlA,843
|
48
|
+
panther/panel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
|
+
panther/panel/apis.py,sha256=COsbwKZyTgyHvHYbpDfusifAH9ojMS3z1KhZCt9M-Ms,2428
|
50
|
+
panther/panel/urls.py,sha256=JiV-H4dWE-m_bfaTTVxzOxTvJmOWhyLOvcbM7xU3Bn4,240
|
51
|
+
panther/panel/utils.py,sha256=0Rv79oR5IEqalqwpRKQHMn1p5duVY5mxMqDKiA5mWx4,437
|
52
|
+
panther-4.0.0.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
|
53
|
+
panther-4.0.0.dist-info/METADATA,sha256=F2RJJ4ZFjxJF_CZJm2Z9b03wW-wlZLZtC0xXHcOK17g,6968
|
54
|
+
panther-4.0.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
55
|
+
panther-4.0.0.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
|
56
|
+
panther-4.0.0.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
|
57
|
+
panther-4.0.0.dist-info/RECORD,,
|
panther/db/connection.py
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
from typing import TYPE_CHECKING
|
2
|
-
|
3
|
-
from pantherdb import PantherDB
|
4
|
-
|
5
|
-
from panther.cli.utils import import_error
|
6
|
-
from panther.configs import config
|
7
|
-
from panther.utils import Singleton
|
8
|
-
|
9
|
-
try:
|
10
|
-
from redis import Redis
|
11
|
-
except ModuleNotFoundError:
|
12
|
-
# This 'Redis' is not going to be used,
|
13
|
-
# If he really wants to use redis,
|
14
|
-
# we are going to force him to install it in 'panther.middlewares.redis'
|
15
|
-
Redis = type('Redis')
|
16
|
-
|
17
|
-
if TYPE_CHECKING:
|
18
|
-
from pymongo.database import Database
|
19
|
-
|
20
|
-
|
21
|
-
class DBSession(Singleton):
|
22
|
-
_db_name: str
|
23
|
-
|
24
|
-
def __init__(self, db_url: str | None = None):
|
25
|
-
if db_url:
|
26
|
-
if (seperator := db_url.find(':')) == -1:
|
27
|
-
# ex: db_url = 'some_db' (or something without ':')
|
28
|
-
seperator = None
|
29
|
-
self._db_name = db_url[:seperator]
|
30
|
-
match self._db_name:
|
31
|
-
case 'mongodb':
|
32
|
-
self._create_mongodb_session(db_url)
|
33
|
-
case 'pantherdb':
|
34
|
-
self._create_pantherdb_session(db_url[12:])
|
35
|
-
case _:
|
36
|
-
msg = f'We are not support "{self._db_name}" database yet'
|
37
|
-
raise ValueError(msg)
|
38
|
-
|
39
|
-
@property
|
40
|
-
def session(self):
|
41
|
-
return self._session
|
42
|
-
|
43
|
-
@property
|
44
|
-
def name(self) -> str:
|
45
|
-
return self._db_name
|
46
|
-
|
47
|
-
def _create_mongodb_session(self, db_url: str) -> None:
|
48
|
-
try:
|
49
|
-
from pymongo import MongoClient
|
50
|
-
except ModuleNotFoundError:
|
51
|
-
msg = "No module named 'pymongo'. Hint: `pip install pymongo`"
|
52
|
-
raise ValueError(msg)
|
53
|
-
self._client: MongoClient = MongoClient(db_url)
|
54
|
-
self._session: Database = self._client.get_database()
|
55
|
-
|
56
|
-
def _create_pantherdb_session(self, db_url: str) -> None:
|
57
|
-
params = {'db_name': db_url, 'return_dict': True}
|
58
|
-
if config['pantherdb_encryption']:
|
59
|
-
try:
|
60
|
-
import cryptography
|
61
|
-
except ModuleNotFoundError as e:
|
62
|
-
import_error(e, package='cryptography')
|
63
|
-
else:
|
64
|
-
params['secret_key'] = config['secret_key']
|
65
|
-
self._session: PantherDB = PantherDB(**params)
|
66
|
-
|
67
|
-
def close(self) -> None:
|
68
|
-
if self._db_name == 'mongodb':
|
69
|
-
self._client.close()
|
70
|
-
else:
|
71
|
-
self._session.close()
|
72
|
-
|
73
|
-
|
74
|
-
class RedisConnection(Singleton, Redis):
|
75
|
-
"""Redis connection here works for per request things (caching, ...)"""
|
76
|
-
|
77
|
-
is_connected: bool = False
|
78
|
-
|
79
|
-
def __init__(self, host: str | None = None, port: int | None = None, **kwargs):
|
80
|
-
if host and port:
|
81
|
-
super().__init__(host=host, port=port, **kwargs)
|
82
|
-
self.is_connected = True
|
83
|
-
|
84
|
-
def execute_command(self, *args, **options) -> any:
|
85
|
-
if not hasattr(self, 'connection_pool'):
|
86
|
-
msg = "'RedisConnection' object has no attribute 'connection_pool'. Hint: Check your redis middleware"
|
87
|
-
raise AttributeError(msg)
|
88
|
-
return super().execute_command(*args, **options)
|
89
|
-
|
90
|
-
|
91
|
-
db: DBSession = DBSession()
|
92
|
-
redis: Redis = RedisConnection()
|