panther 4.2.6__tar.gz → 4.3.1__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.2.6 → panther-4.3.1}/PKG-INFO +2 -1
- {panther-4.2.6 → panther-4.3.1}/panther/__init__.py +1 -1
- {panther-4.2.6 → panther-4.3.1}/panther/_load_configs.py +18 -1
- {panther-4.2.6 → panther-4.3.1}/panther/_utils.py +9 -9
- {panther-4.2.6 → panther-4.3.1}/panther/configs.py +2 -0
- {panther-4.2.6 → panther-4.3.1}/panther/main.py +9 -11
- {panther-4.2.6 → panther-4.3.1}/panther/response.py +37 -0
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/PKG-INFO +2 -1
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/requires.txt +1 -0
- {panther-4.2.6 → panther-4.3.1}/setup.py +1 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_response.py +35 -3
- {panther-4.2.6 → panther-4.3.1}/tests/test_utils.py +54 -0
- {panther-4.2.6 → panther-4.3.1}/LICENSE +0 -0
- {panther-4.2.6 → panther-4.3.1}/README.md +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/app.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/authentications.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/background_tasks.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/base_request.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/base_websocket.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/caching.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/__init__.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/create_command.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/main.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/monitor_command.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/run_command.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/template.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/cli/utils.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/__init__.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/connections.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/cursor.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/models.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/queries/__init__.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/queries/base_queries.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/queries/mongodb_queries.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/queries/pantherdb_queries.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/queries/queries.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/db/utils.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/events.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/exceptions.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/file_handler.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/generics.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/logging.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/middlewares/__init__.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/middlewares/base.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/monitoring.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/pagination.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/panel/__init__.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/panel/apis.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/panel/urls.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/panel/utils.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/permissions.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/request.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/routings.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/serializer.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/status.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/test.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/throttling.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/utils.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther/websocket.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/SOURCES.txt +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/dependency_links.txt +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/entry_points.txt +0 -0
- {panther-4.2.6 → panther-4.3.1}/panther.egg-info/top_level.txt +0 -0
- {panther-4.2.6 → panther-4.3.1}/pyproject.toml +0 -0
- {panther-4.2.6 → panther-4.3.1}/setup.cfg +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_authentication.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_background_tasks.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_caching.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_cli.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_database.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_events.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_generics.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_multipart.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_panel_apis.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_request.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_routing.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_run.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_serializer.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_status.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_throttling.py +0 -0
- {panther-4.2.6 → panther-4.3.1}/tests/test_websockets.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.3.1
|
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
|
@@ -19,6 +19,7 @@ Requires-Dist: pydantic~=2.7.4
|
|
19
19
|
Requires-Dist: rich~=13.7.1
|
20
20
|
Requires-Dist: uvicorn~=0.27.1
|
21
21
|
Requires-Dist: pytz~=2024.1
|
22
|
+
Requires-Dist: Jinja2~=3.1
|
22
23
|
Provides-Extra: full
|
23
24
|
Requires-Dist: redis==5.0.1; extra == "full"
|
24
25
|
Requires-Dist: motor~=3.5.0; extra == "full"
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
import sys
|
3
|
+
import types
|
3
4
|
from importlib import import_module
|
4
5
|
from multiprocessing import Manager
|
5
6
|
|
6
|
-
from panther._utils import import_class
|
7
|
+
from panther._utils import import_class, check_function_type_endpoint, check_class_type_endpoint
|
7
8
|
from panther.background_tasks import background_tasks
|
8
9
|
from panther.base_websocket import WebsocketConnections
|
9
10
|
from panther.cli.utils import import_error
|
@@ -29,12 +30,14 @@ __all__ = (
|
|
29
30
|
'load_user_model',
|
30
31
|
'load_log_queries',
|
31
32
|
'load_middlewares',
|
33
|
+
'load_templates_dir',
|
32
34
|
'load_auto_reformat',
|
33
35
|
'load_background_tasks',
|
34
36
|
'load_default_cache_exp',
|
35
37
|
'load_authentication_class',
|
36
38
|
'load_urls',
|
37
39
|
'load_websocket_connections',
|
40
|
+
'check_endpoints_inheritance',
|
38
41
|
)
|
39
42
|
|
40
43
|
logger = logging.getLogger('panther')
|
@@ -82,6 +85,11 @@ def load_timezone(_configs: dict, /) -> None:
|
|
82
85
|
config.TIMEZONE = timezone
|
83
86
|
|
84
87
|
|
88
|
+
def load_templates_dir(_configs: dict, /) -> None:
|
89
|
+
if templates_dir := _configs.get('TEMPLATES_DIR'):
|
90
|
+
config.TEMPLATES_DIR = templates_dir
|
91
|
+
|
92
|
+
|
85
93
|
def load_database(_configs: dict, /) -> None:
|
86
94
|
database_config = _configs.get('DATABASE', {})
|
87
95
|
if 'engine' in database_config:
|
@@ -259,5 +267,14 @@ def load_websocket_connections():
|
|
259
267
|
config.WEBSOCKET_CONNECTIONS = WebsocketConnections(pubsub_connection=pubsub_connection)
|
260
268
|
|
261
269
|
|
270
|
+
def check_endpoints_inheritance():
|
271
|
+
"""Should be after `load_urls()`"""
|
272
|
+
for _, endpoint in config.FLAT_URLS.items():
|
273
|
+
if isinstance(endpoint, types.FunctionType):
|
274
|
+
check_function_type_endpoint(endpoint=endpoint)
|
275
|
+
else:
|
276
|
+
check_class_type_endpoint(endpoint=endpoint)
|
277
|
+
|
278
|
+
|
262
279
|
def _exception_handler(field: str, error: str | Exception) -> PantherError:
|
263
280
|
return PantherError(f"Invalid '{field}': {error}")
|
@@ -4,12 +4,13 @@ import logging
|
|
4
4
|
import re
|
5
5
|
import subprocess
|
6
6
|
import types
|
7
|
-
from typing import Any, Generator, Iterator, AsyncGenerator
|
8
7
|
from collections.abc import Callable
|
9
8
|
from traceback import TracebackException
|
9
|
+
from typing import Any, Generator, Iterator, AsyncGenerator
|
10
10
|
|
11
11
|
from panther.exceptions import PantherError
|
12
12
|
from panther.file_handler import File
|
13
|
+
from panther.websocket import GenericWebsocket
|
13
14
|
|
14
15
|
logger = logging.getLogger('panther')
|
15
16
|
|
@@ -99,19 +100,18 @@ def reformat_code(base_dir):
|
|
99
100
|
def check_function_type_endpoint(endpoint: types.FunctionType) -> Callable:
|
100
101
|
# Function Doesn't Have @API Decorator
|
101
102
|
if not hasattr(endpoint, '__wrapped__'):
|
102
|
-
|
103
|
-
|
104
|
-
return endpoint
|
103
|
+
raise PantherError(
|
104
|
+
f'You may have forgotten to use `@API()` on the `{endpoint.__module__}.{endpoint.__name__}()`')
|
105
105
|
|
106
106
|
|
107
107
|
def check_class_type_endpoint(endpoint: Callable) -> Callable:
|
108
108
|
from panther.app import GenericAPI
|
109
109
|
|
110
|
-
if not issubclass(endpoint, GenericAPI):
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
110
|
+
if not issubclass(endpoint, (GenericAPI, GenericWebsocket)):
|
111
|
+
raise PantherError(
|
112
|
+
f'You may have forgotten to inherit from `panther.app.GenericAPI` or `panther.app.GenericWebsocket` '
|
113
|
+
f'on the `{endpoint.__module__}.{endpoint.__name__}()`'
|
114
|
+
)
|
115
115
|
|
116
116
|
|
117
117
|
def async_next(iterator: Iterator):
|
@@ -67,6 +67,7 @@ class Config:
|
|
67
67
|
STARTUPS: list[Callable]
|
68
68
|
SHUTDOWNS: list[Callable]
|
69
69
|
TIMEZONE: str
|
70
|
+
TEMPLATES_DIR: str | list[str]
|
70
71
|
AUTO_REFORMAT: bool
|
71
72
|
QUERY_ENGINE: typing.Callable | None
|
72
73
|
DATABASE: typing.Callable | None
|
@@ -110,6 +111,7 @@ default_configs = {
|
|
110
111
|
'STARTUPS': [],
|
111
112
|
'SHUTDOWNS': [],
|
112
113
|
'TIMEZONE': 'UTC',
|
114
|
+
'TEMPLATES_DIR': 'templates',
|
113
115
|
'AUTO_REFORMAT': False,
|
114
116
|
'QUERY_ENGINE': None,
|
115
117
|
'DATABASE': None,
|
@@ -58,6 +58,7 @@ class Panther:
|
|
58
58
|
load_throttling(self._configs_module)
|
59
59
|
load_user_model(self._configs_module)
|
60
60
|
load_log_queries(self._configs_module)
|
61
|
+
load_templates_dir(self._configs_module)
|
61
62
|
load_middlewares(self._configs_module)
|
62
63
|
load_auto_reformat(self._configs_module)
|
63
64
|
load_background_tasks(self._configs_module)
|
@@ -66,6 +67,8 @@ class Panther:
|
|
66
67
|
load_urls(self._configs_module, urls=self._urls)
|
67
68
|
load_websocket_connections()
|
68
69
|
|
70
|
+
check_endpoints_inheritance()
|
71
|
+
|
69
72
|
async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
|
70
73
|
if scope['type'] == 'lifespan':
|
71
74
|
message = await receive()
|
@@ -159,19 +162,10 @@ class Panther:
|
|
159
162
|
await request.read_body()
|
160
163
|
|
161
164
|
# Find Endpoint
|
162
|
-
|
163
|
-
if
|
165
|
+
endpoint, found_path = find_endpoint(path=request.path)
|
166
|
+
if endpoint is None:
|
164
167
|
return await self._raise(send, monitoring=monitoring, status_code=status.HTTP_404_NOT_FOUND)
|
165
168
|
|
166
|
-
# Check Endpoint Type
|
167
|
-
try:
|
168
|
-
if isinstance(_endpoint, types.FunctionType):
|
169
|
-
endpoint = check_function_type_endpoint(endpoint=_endpoint)
|
170
|
-
else:
|
171
|
-
endpoint = check_class_type_endpoint(endpoint=_endpoint)
|
172
|
-
except TypeError:
|
173
|
-
return await self._raise(send, monitoring=monitoring, status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
174
|
-
|
175
169
|
# Collect Path Variables
|
176
170
|
request.collect_path_variables(found_path=found_path)
|
177
171
|
|
@@ -185,6 +179,10 @@ class Panther:
|
|
185
179
|
f'Make sure to return the `request` at the end of `{middleware.__class__.__name__}.before()`')
|
186
180
|
return await self._raise(send, monitoring=monitoring)
|
187
181
|
|
182
|
+
# Prepare the method
|
183
|
+
if not isinstance(endpoint, types.FunctionType):
|
184
|
+
endpoint = endpoint().call_method
|
185
|
+
|
188
186
|
# Call Endpoint
|
189
187
|
response = await endpoint(request=request)
|
190
188
|
|
@@ -1,11 +1,22 @@
|
|
1
1
|
import asyncio
|
2
|
+
from sys import version_info
|
2
3
|
from types import NoneType
|
3
4
|
from typing import Generator, AsyncGenerator, Any, Type
|
4
5
|
|
6
|
+
if version_info >= (3, 11):
|
7
|
+
from typing import LiteralString
|
8
|
+
else:
|
9
|
+
from typing import TypeVar
|
10
|
+
|
11
|
+
LiteralString = TypeVar('LiteralString')
|
12
|
+
|
13
|
+
|
5
14
|
import orjson as json
|
6
15
|
from pydantic import BaseModel
|
16
|
+
from jinja2 import Environment, FileSystemLoader
|
7
17
|
|
8
18
|
from panther import status
|
19
|
+
from panther.configs import config
|
9
20
|
from panther._utils import to_async_generator
|
10
21
|
from panther.db.cursor import Cursor
|
11
22
|
from pantherdb import Cursor as PantherDBCursor
|
@@ -215,3 +226,29 @@ class PlainTextResponse(Response):
|
|
215
226
|
if isinstance(self.data, bytes):
|
216
227
|
return self.data
|
217
228
|
return self.data.encode()
|
229
|
+
|
230
|
+
|
231
|
+
class TemplateResponse(HTMLResponse):
|
232
|
+
environment = Environment(loader=FileSystemLoader(config.TEMPLATES_DIR))
|
233
|
+
|
234
|
+
def __init__(
|
235
|
+
self,
|
236
|
+
source: str | LiteralString | NoneType = None,
|
237
|
+
path: str | NoneType = None,
|
238
|
+
context: dict | NoneType = None,
|
239
|
+
headers: dict | NoneType = None,
|
240
|
+
status_code: int = status.HTTP_200_OK,
|
241
|
+
pagination: Pagination | NoneType = None,
|
242
|
+
):
|
243
|
+
"""
|
244
|
+
:param source: should be a string
|
245
|
+
:param path: should be path of template file
|
246
|
+
:param context: should be dict of items
|
247
|
+
:param headers: should be dict of headers
|
248
|
+
:param status_code: should be int
|
249
|
+
:param pagination: instance of Pagination or None
|
250
|
+
Its template() method will be used
|
251
|
+
"""
|
252
|
+
|
253
|
+
template = self.environment.get_template(path) if path is not None else self.environment.from_string(source)
|
254
|
+
super().__init__(template.render(context), headers, status_code, pagination=pagination)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.3.1
|
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
|
@@ -19,6 +19,7 @@ Requires-Dist: pydantic~=2.7.4
|
|
19
19
|
Requires-Dist: rich~=13.7.1
|
20
20
|
Requires-Dist: uvicorn~=0.27.1
|
21
21
|
Requires-Dist: pytz~=2024.1
|
22
|
+
Requires-Dist: Jinja2~=3.1
|
22
23
|
Provides-Extra: full
|
23
24
|
Requires-Dist: redis==5.0.1; extra == "full"
|
24
25
|
Requires-Dist: motor~=3.5.0; extra == "full"
|
@@ -2,7 +2,7 @@ from unittest import IsolatedAsyncioTestCase
|
|
2
2
|
|
3
3
|
from panther import Panther
|
4
4
|
from panther.app import API, GenericAPI
|
5
|
-
from panther.response import Response, HTMLResponse, PlainTextResponse, StreamingResponse
|
5
|
+
from panther.response import Response, HTMLResponse, PlainTextResponse, StreamingResponse, TemplateResponse
|
6
6
|
from panther.test import APIClient
|
7
7
|
|
8
8
|
|
@@ -116,6 +116,18 @@ class ReturnHTMLResponse(GenericAPI):
|
|
116
116
|
return HTMLResponse('<html><head><title></title></head></html>')
|
117
117
|
|
118
118
|
|
119
|
+
@API()
|
120
|
+
async def return_template_response() -> TemplateResponse:
|
121
|
+
return TemplateResponse(source='<html><body><p>{{ content }}</p></body></html>', context={'content': 'Hello World'})
|
122
|
+
|
123
|
+
|
124
|
+
class ReturnTemplateResponse(GenericAPI):
|
125
|
+
def get(self) -> TemplateResponse:
|
126
|
+
return TemplateResponse(
|
127
|
+
source='<html><body><p>{{ content }}</p></body></html>', context={'content': 'Hello World'}
|
128
|
+
)
|
129
|
+
|
130
|
+
|
119
131
|
@API()
|
120
132
|
async def return_plain_response():
|
121
133
|
return PlainTextResponse('Hello World')
|
@@ -160,7 +172,7 @@ urls = {
|
|
160
172
|
'response-tuple': return_response_tuple,
|
161
173
|
'html': return_html_response,
|
162
174
|
'plain': return_plain_response,
|
163
|
-
|
175
|
+
'template': return_template_response,
|
164
176
|
'nothing-cls': ReturnNothing,
|
165
177
|
'none-cls': ReturnNone,
|
166
178
|
'dict-cls': ReturnDict,
|
@@ -172,8 +184,8 @@ urls = {
|
|
172
184
|
'response-list-cls': ReturnResponseList,
|
173
185
|
'response-tuple-cls': ReturnResponseTuple,
|
174
186
|
'html-cls': ReturnHTMLResponse,
|
187
|
+
'template-cls': ReturnTemplateResponse,
|
175
188
|
'plain-cls': ReturnPlainResponse,
|
176
|
-
|
177
189
|
'stream': ReturnStreamingResponse,
|
178
190
|
'async-stream': ReturnAsyncStreamingResponse,
|
179
191
|
'invalid-status-code': ReturnInvalidStatusCode,
|
@@ -406,6 +418,26 @@ class TestResponses(IsolatedAsyncioTestCase):
|
|
406
418
|
assert res.headers['Access-Control-Allow-Origin'] == '*'
|
407
419
|
assert res.headers['Content-Length'] == '41'
|
408
420
|
|
421
|
+
async def test_response_template(self) -> None:
|
422
|
+
res: Response = await self.client.get('template/')
|
423
|
+
assert res.status_code == 200
|
424
|
+
assert res.data == '<html><body><p>Hello World</p></body></html>'
|
425
|
+
assert res.body == b'<html><body><p>Hello World</p></body></html>'
|
426
|
+
assert set(res.headers.keys()) == {'Content-Type', 'Access-Control-Allow-Origin', 'Content-Length'}
|
427
|
+
assert res.headers['Content-Type'] == 'text/html; charset=utf-8'
|
428
|
+
assert res.headers['Access-Control-Allow-Origin'] == '*'
|
429
|
+
assert res.headers['Content-Length'] == '44'
|
430
|
+
|
431
|
+
async def test_response_template_cls(self) -> None:
|
432
|
+
res: Response = await self.client.get('template-cls/')
|
433
|
+
assert res.status_code == 200
|
434
|
+
assert res.data == '<html><body><p>Hello World</p></body></html>'
|
435
|
+
assert res.body == b'<html><body><p>Hello World</p></body></html>'
|
436
|
+
assert set(res.headers.keys()) == {'Content-Type', 'Access-Control-Allow-Origin', 'Content-Length'}
|
437
|
+
assert res.headers['Content-Type'] == 'text/html; charset=utf-8'
|
438
|
+
assert res.headers['Access-Control-Allow-Origin'] == '*'
|
439
|
+
assert res.headers['Content-Length'] == '44'
|
440
|
+
|
409
441
|
async def test_response_plain(self):
|
410
442
|
res = await self.client.get('plain/')
|
411
443
|
assert res.status_code == 200
|
@@ -379,6 +379,60 @@ class TestLoadConfigs(TestCase):
|
|
379
379
|
AUTHENTICATION = None
|
380
380
|
SECRET_KEY = None
|
381
381
|
|
382
|
+
def test_check_function_endpoint_decorator(self):
|
383
|
+
with self.assertLogs(level='ERROR') as captured:
|
384
|
+
try:
|
385
|
+
Panther(name=__name__, configs=__name__, urls={'/': invalid_api})
|
386
|
+
except SystemExit:
|
387
|
+
assert True
|
388
|
+
else:
|
389
|
+
assert False
|
390
|
+
|
391
|
+
assert len(captured.records) == 1
|
392
|
+
assert captured.records[0].getMessage() == 'You may have forgotten to use `@API()` on the `tests.test_utils.invalid_api()`'
|
393
|
+
|
394
|
+
def test_check_class_endpoint_inheritance(self):
|
395
|
+
with self.assertLogs(level='ERROR') as captured:
|
396
|
+
try:
|
397
|
+
Panther(name=__name__, configs=__name__, urls={'/': InvalidAPI})
|
398
|
+
except SystemExit:
|
399
|
+
assert True
|
400
|
+
else:
|
401
|
+
assert False
|
402
|
+
|
403
|
+
assert len(captured.records) == 1
|
404
|
+
assert captured.records[0].getMessage() == (
|
405
|
+
f'You may have forgotten to inherit from `panther.app.GenericAPI` or `panther.app.GenericWebsocket` '
|
406
|
+
f'on the `tests.test_utils.InvalidAPI()`'
|
407
|
+
)
|
408
|
+
|
409
|
+
def test_check_websocket_inheritance(self):
|
410
|
+
with self.assertLogs(level='ERROR') as captured:
|
411
|
+
try:
|
412
|
+
Panther(name=__name__, configs=__name__, urls={'/': InvalidWebsocket})
|
413
|
+
except SystemExit:
|
414
|
+
assert True
|
415
|
+
else:
|
416
|
+
assert False
|
417
|
+
|
418
|
+
assert len(captured.records) == 1
|
419
|
+
assert captured.records[0].getMessage() == (
|
420
|
+
f'You may have forgotten to inherit from `panther.app.GenericAPI` or `panther.app.GenericWebsocket` '
|
421
|
+
f'on the `tests.test_utils.InvalidWebsocket()`'
|
422
|
+
)
|
423
|
+
|
424
|
+
|
425
|
+
def invalid_api():
|
426
|
+
pass
|
427
|
+
|
428
|
+
|
429
|
+
class InvalidAPI:
|
430
|
+
pass
|
431
|
+
|
432
|
+
|
433
|
+
class InvalidWebsocket:
|
434
|
+
pass
|
435
|
+
|
382
436
|
|
383
437
|
class CorrectTestMiddleware(BaseMiddleware):
|
384
438
|
pass
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|