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.
Files changed (116) hide show
  1. panther-4.1.3/PKG-INFO +204 -0
  2. panther-4.1.3/README.md +173 -0
  3. {panther-3.3.1 → panther-4.1.3}/panther/__init__.py +1 -1
  4. panther-4.1.3/panther/_load_configs.py +263 -0
  5. {panther-3.3.1 → panther-4.1.3}/panther/_utils.py +53 -70
  6. panther-4.1.3/panther/app.py +213 -0
  7. panther-4.1.3/panther/authentications.py +166 -0
  8. {panther-3.3.1 → panther-4.1.3}/panther/background_tasks.py +25 -14
  9. {panther-3.3.1 → panther-4.1.3}/panther/base_request.py +43 -21
  10. panther-4.1.3/panther/base_websocket.py +278 -0
  11. panther-4.1.3/panther/caching.py +125 -0
  12. panther-4.1.3/panther/cli/create_command.py +255 -0
  13. panther-4.1.3/panther/cli/monitor_command.py +97 -0
  14. {panther-3.3.1 → panther-4.1.3}/panther/cli/run_command.py +3 -1
  15. panther-4.1.3/panther/cli/template.py +231 -0
  16. {panther-3.3.1 → panther-4.1.3}/panther/cli/utils.py +43 -17
  17. panther-4.1.3/panther/configs.py +118 -0
  18. panther-4.1.3/panther/db/connections.py +145 -0
  19. panther-4.1.3/panther/db/cursor.py +43 -0
  20. panther-4.1.3/panther/db/models.py +84 -0
  21. panther-4.1.3/panther/db/queries/__init__.py +1 -0
  22. panther-4.1.3/panther/db/queries/base_queries.py +127 -0
  23. panther-4.1.3/panther/db/queries/mongodb_queries.py +143 -0
  24. panther-4.1.3/panther/db/queries/pantherdb_queries.py +110 -0
  25. panther-4.1.3/panther/db/queries/queries.py +395 -0
  26. {panther-3.3.1 → panther-4.1.3}/panther/db/utils.py +17 -23
  27. panther-4.1.3/panther/events.py +44 -0
  28. {panther-3.3.1 → panther-4.1.3}/panther/exceptions.py +26 -12
  29. {panther-3.3.1 → panther-4.1.3}/panther/file_handler.py +20 -3
  30. panther-4.1.3/panther/generics.py +191 -0
  31. {panther-3.3.1 → panther-4.1.3}/panther/logging.py +8 -3
  32. panther-4.1.3/panther/main.py +231 -0
  33. panther-4.1.3/panther/middlewares/base.py +30 -0
  34. {panther-3.3.1 → panther-4.1.3}/panther/monitoring.py +8 -5
  35. panther-4.1.3/panther/pagination.py +51 -0
  36. {panther-3.3.1 → panther-4.1.3}/panther/panel/apis.py +32 -5
  37. {panther-3.3.1 → panther-4.1.3}/panther/panel/urls.py +2 -1
  38. panther-4.1.3/panther/permissions.py +13 -0
  39. {panther-3.3.1 → panther-4.1.3}/panther/request.py +6 -13
  40. panther-4.1.3/panther/response.py +217 -0
  41. panther-4.1.3/panther/routings.py +189 -0
  42. panther-4.1.3/panther/serializer.py +231 -0
  43. {panther-3.3.1 → panther-4.1.3}/panther/test.py +81 -23
  44. panther-4.1.3/panther/utils.py +113 -0
  45. panther-4.1.3/panther/websocket.py +37 -0
  46. panther-4.1.3/panther.egg-info/PKG-INFO +204 -0
  47. {panther-3.3.1 → panther-4.1.3}/panther.egg-info/SOURCES.txt +16 -7
  48. panther-4.1.3/panther.egg-info/requires.txt +16 -0
  49. panther-4.1.3/pyproject.toml +11 -0
  50. {panther-3.3.1 → panther-4.1.3}/setup.py +11 -9
  51. panther-4.1.3/tests/test_authentication.py +170 -0
  52. {panther-3.3.1 → panther-4.1.3}/tests/test_background_tasks.py +32 -39
  53. {panther-3.3.1 → panther-4.1.3}/tests/test_caching.py +21 -20
  54. panther-4.1.3/tests/test_cli.py +147 -0
  55. panther-4.1.3/tests/test_database.py +524 -0
  56. panther-4.1.3/tests/test_events.py +123 -0
  57. panther-4.1.3/tests/test_generics.py +226 -0
  58. {panther-3.3.1 → panther-4.1.3}/tests/test_multipart.py +10 -10
  59. {panther-3.3.1 → panther-4.1.3}/tests/test_panel_apis.py +10 -7
  60. panther-4.1.3/tests/test_request.py +447 -0
  61. panther-4.1.3/tests/test_response.py +456 -0
  62. {panther-3.3.1 → panther-4.1.3}/tests/test_routing.py +227 -150
  63. panther-4.1.3/tests/test_run.py +67 -0
  64. panther-4.1.3/tests/test_serializer.py +399 -0
  65. panther-4.1.3/tests/test_throttling.py +74 -0
  66. {panther-3.3.1 → panther-4.1.3}/tests/test_utils.py +90 -49
  67. panther-4.1.3/tests/test_websockets.py +330 -0
  68. panther-3.3.1/PKG-INFO +0 -229
  69. panther-3.3.1/README.md +0 -200
  70. panther-3.3.1/panther/_load_configs.py +0 -246
  71. panther-3.3.1/panther/app.py +0 -224
  72. panther-3.3.1/panther/authentications.py +0 -128
  73. panther-3.3.1/panther/base_websocket.py +0 -152
  74. panther-3.3.1/panther/caching.py +0 -90
  75. panther-3.3.1/panther/cli/create_command.py +0 -106
  76. panther-3.3.1/panther/cli/monitor_command.py +0 -71
  77. panther-3.3.1/panther/cli/template.py +0 -137
  78. panther-3.3.1/panther/configs.py +0 -77
  79. panther-3.3.1/panther/db/connection.py +0 -73
  80. panther-3.3.1/panther/db/models.py +0 -47
  81. panther-3.3.1/panther/db/queries/__init__.py +0 -1
  82. panther-3.3.1/panther/db/queries/mongodb_queries.py +0 -89
  83. panther-3.3.1/panther/db/queries/pantherdb_queries.py +0 -81
  84. panther-3.3.1/panther/db/queries/queries.py +0 -269
  85. panther-3.3.1/panther/main.py +0 -305
  86. panther-3.3.1/panther/middlewares/base.py +0 -10
  87. panther-3.3.1/panther/middlewares/db.py +0 -18
  88. panther-3.3.1/panther/middlewares/redis.py +0 -43
  89. panther-3.3.1/panther/permissions.py +0 -13
  90. panther-3.3.1/panther/response.py +0 -104
  91. panther-3.3.1/panther/routings.py +0 -169
  92. panther-3.3.1/panther/utils.py +0 -59
  93. panther-3.3.1/panther/websocket.py +0 -62
  94. panther-3.3.1/panther.egg-info/PKG-INFO +0 -229
  95. panther-3.3.1/panther.egg-info/requires.txt +0 -14
  96. panther-3.3.1/pyproject.toml +0 -6
  97. panther-3.3.1/tests/test_cli.py +0 -86
  98. panther-3.3.1/tests/test_mongodb.py +0 -48
  99. panther-3.3.1/tests/test_pantherdb.py +0 -445
  100. panther-3.3.1/tests/test_request.py +0 -295
  101. panther-3.3.1/tests/test_run.py +0 -70
  102. panther-3.3.1/tests/test_simple_responses.py +0 -116
  103. {panther-3.3.1 → panther-4.1.3}/LICENSE +0 -0
  104. {panther-3.3.1 → panther-4.1.3}/panther/cli/__init__.py +0 -0
  105. {panther-3.3.1 → panther-4.1.3}/panther/cli/main.py +0 -0
  106. {panther-3.3.1 → panther-4.1.3}/panther/db/__init__.py +0 -0
  107. {panther-3.3.1 → panther-4.1.3}/panther/middlewares/__init__.py +0 -0
  108. {panther-3.3.1 → panther-4.1.3}/panther/panel/__init__.py +0 -0
  109. {panther-3.3.1 → panther-4.1.3}/panther/panel/utils.py +0 -0
  110. {panther-3.3.1 → panther-4.1.3}/panther/status.py +0 -0
  111. {panther-3.3.1 → panther-4.1.3}/panther/throttling.py +0 -0
  112. {panther-3.3.1 → panther-4.1.3}/panther.egg-info/dependency_links.txt +0 -0
  113. {panther-3.3.1 → panther-4.1.3}/panther.egg-info/entry_points.txt +0 -0
  114. {panther-3.3.1 → panther-4.1.3}/panther.egg-info/top_level.txt +0 -0
  115. {panther-3.3.1 → panther-4.1.3}/setup.cfg +0 -0
  116. {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
+ [![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)
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
+ ![diagram](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/diagram.png)
195
+
196
+ ---
197
+
198
+ ### Roadmap
199
+
200
+ ![roadmap](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/roadmap.jpg)
201
+
202
+ ---
203
+
204
+ **If you find this project useful, please give it a star ⭐️.**
@@ -0,0 +1,173 @@
1
+
2
+ [![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)
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
+ ![diagram](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/diagram.png)
164
+
165
+ ---
166
+
167
+ ### Roadmap
168
+
169
+ ![roadmap](https://raw.githubusercontent.com/AliRn76/panther/master/docs/docs/images/roadmap.jpg)
170
+
171
+ ---
172
+
173
+ **If you find this project useful, please give it a star ⭐️.**
@@ -1,6 +1,6 @@
1
1
  from panther.main import Panther # noqa: F401
2
2
 
3
- __version__ = '3.3.1'
3
+ __version__ = '4.1.3'
4
4
 
5
5
 
6
6
  def version():
@@ -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}")