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.
Files changed (52) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +168 -171
  3. panther/_utils.py +26 -49
  4. panther/app.py +85 -105
  5. panther/authentications.py +86 -55
  6. panther/background_tasks.py +25 -14
  7. panther/base_request.py +38 -14
  8. panther/base_websocket.py +172 -94
  9. panther/caching.py +60 -25
  10. panther/cli/create_command.py +20 -10
  11. panther/cli/monitor_command.py +63 -37
  12. panther/cli/template.py +40 -20
  13. panther/cli/utils.py +32 -18
  14. panther/configs.py +65 -58
  15. panther/db/connections.py +139 -0
  16. panther/db/cursor.py +43 -0
  17. panther/db/models.py +64 -29
  18. panther/db/queries/__init__.py +1 -1
  19. panther/db/queries/base_queries.py +127 -0
  20. panther/db/queries/mongodb_queries.py +77 -38
  21. panther/db/queries/pantherdb_queries.py +59 -30
  22. panther/db/queries/queries.py +232 -117
  23. panther/db/utils.py +17 -18
  24. panther/events.py +44 -0
  25. panther/exceptions.py +26 -12
  26. panther/file_handler.py +2 -2
  27. panther/generics.py +163 -0
  28. panther/logging.py +7 -2
  29. panther/main.py +111 -188
  30. panther/middlewares/base.py +3 -0
  31. panther/monitoring.py +8 -5
  32. panther/pagination.py +48 -0
  33. panther/panel/apis.py +32 -5
  34. panther/panel/urls.py +2 -1
  35. panther/permissions.py +3 -3
  36. panther/request.py +6 -13
  37. panther/response.py +114 -34
  38. panther/routings.py +83 -66
  39. panther/serializer.py +214 -33
  40. panther/test.py +31 -21
  41. panther/utils.py +28 -16
  42. panther/websocket.py +7 -4
  43. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/METADATA +93 -71
  44. panther-4.0.0.dist-info/RECORD +57 -0
  45. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/WHEEL +1 -1
  46. panther/db/connection.py +0 -92
  47. panther/middlewares/db.py +0 -18
  48. panther/middlewares/redis.py +0 -47
  49. panther-3.8.2.dist-info/RECORD +0 -54
  50. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/LICENSE +0 -0
  51. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/entry_points.txt +0 -0
  52. {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 = data
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
- return Response(
58
- data=json.loads(self.response.get('body', b'null')),
59
- status_code=self.header['status'],
60
- headers=self.header['headers'],
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 asyncio.run(
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
- from datetime import datetime, timedelta
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
- logger.critical(f'"{env_file}" is not valid file for load_env()')
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
- else:
85
- return derived_key
87
+ return derived_key
88
+
86
89
 
90
+ class ULID:
91
+ """https://github.com/ulid/spec"""
87
92
 
88
- def encrypt_password(password: str) -> str:
89
- salt = os.urandom(URANDOM_SIZE)
90
- derived_key = scrypt(password=password, salt=salt, digest=True)
93
+ crockford_base32_characters = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
91
94
 
92
- return f'{salt.hex()}{derived_key}'
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
- return derived_key == stored_hash
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
- We are using this method to send message to the client,
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.websocket_connections.publish(connection_id=connection_id, action='send', data=data)
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.websocket_connections.publish(connection_id=connection_id, action='close', data=data)
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.8.2
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.3.5
19
- Requires-Dist: pydantic ~=2.1
20
- Requires-Dist: rich ~=13.5
21
- Requires-Dist: uvicorn ~=0.23
22
- Requires-Dist: watchfiles ~=0.19
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: pymongo ~=4.4 ; extra == 'full'
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 ~=41.0 ; extra == 'full'
29
+ Requires-Dist: cryptography ~=42.0 ; extra == 'full'
30
+ Requires-Dist: watchfiles ~=0.21.0 ; extra == 'full'
31
31
 
32
32
 
33
33
  [![PyPI](https://img.shields.io/pypi/v/panther?label=PyPI)](https://pypi.org/project/panther/) [![PyVersion](https://img.shields.io/pypi/pyversions/panther.svg)](https://pypi.org/project/panther/) [![codecov](https://codecov.io/github/AliRn76/panther/graph/badge.svg?token=YWFQA43GSP)](https://codecov.io/github/AliRn76/panther) [![Downloads](https://static.pepy.tech/badge/panther/month)](https://pepy.tech/project/panther) [![license](https://img.shields.io/github/license/alirn76/panther.svg)](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
- - Document-oriented Databases ODM ([PantherDB](https://pypi.org/project/pantherdb/), MongoDB)
49
- - Built-in Websocket Support
50
- - Cache APIs (In Memory, In Redis)
51
- - Built-in Authentication Classes (Customizable)
52
- - Built-in Permission Classes (Customizable)
53
- - Handle Custom Middlewares
54
- - Handle Custom Throttling
55
- - Visual API Monitoring (In Terminal)
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
- - <details>
88
- <summary>1. Create a Virtual Environment</summary>
89
- <pre>$ python3 -m venv .venv</pre>
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
- ```console
99
+ ```shell
117
100
  $ panther create
118
101
  ```
119
102
 
120
103
  - #### Run Project
121
104
 
122
- ```console
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
- ```console
112
+ ```shell
130
113
  $ panther monitor
131
114
  ```
132
115
 
133
116
  - #### Python Shell
134
117
 
135
- ```console
118
+ ```shell
136
119
  $ panther shell
137
120
  ```
138
121
 
139
122
  ---
140
123
 
141
- ### Single-File Structure Example
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 version, status, Panther
148
- from panther.app import API
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
- @API()
157
- async def hello_world():
158
- return {'detail': 'Hello World'}
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
- @API(cache=True, throttling=InfoThrottling)
162
- async def info(request: Request):
163
- data = {
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
- '': hello_world,
173
- 'info': info,
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
- > **Real Word Example: [Https://GitHub.com/PantherPy/panther-example](https://GitHub.com/PantherPy/panther-example)**
209
+ ---
210
+
211
+ ### How Panther Works!
212
+
213
+ ![diagram](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/diagram.png)
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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()