CheeseAPI 2.0.4b1__tar.gz → 2.0.5b3__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.
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/app.py +56 -22
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/cors.py +1 -1
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/request.py +7 -1
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/response.py +13 -5
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/route.py +3 -1
- cheeseapi-2.0.5b3/CheeseAPI/scheduler.py +619 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/signal.py +0 -16
- cheeseapi-2.0.5b3/CheeseAPI/static.py +10 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/validator.py +1 -1
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/websocket.py +26 -33
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/PKG-INFO +1 -1
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/pyproject.toml +1 -1
- cheeseapi-2.0.4b1/CheeseAPI/scheduler.py +0 -300
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/.gitignore +0 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/__init__.py +0 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/file.py +0 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/CheeseAPI/printer.py +0 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/LICENSE +0 -0
- {cheeseapi-2.0.4b1 → cheeseapi-2.0.5b3}/README.md +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import os, pathlib, multiprocessing, ssl, socket, asyncio, concurrent.futures, inspect
|
|
1
|
+
import os, pathlib, multiprocessing, ssl, socket, asyncio, concurrent.futures, inspect, importlib.util
|
|
2
2
|
from typing import Type, Literal, Callable, AsyncIterable, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import signal, redis
|
|
5
5
|
from CheeseLog import CheeseLogger, Message
|
|
6
6
|
|
|
7
|
+
from CheeseAPI import static
|
|
7
8
|
from CheeseAPI.printer import Printer
|
|
8
9
|
from CheeseAPI.signal import Signal
|
|
9
10
|
from CheeseAPI.request import RequestProxy
|
|
@@ -31,6 +32,8 @@ class AppProxy:
|
|
|
31
32
|
def start(self):
|
|
32
33
|
signal.signal(signal.SIGTERM, lambda signum, frame: self.app.stop())
|
|
33
34
|
|
|
35
|
+
self._process_init()
|
|
36
|
+
|
|
34
37
|
waiting_list = []
|
|
35
38
|
|
|
36
39
|
try:
|
|
@@ -98,7 +101,31 @@ class AppProxy:
|
|
|
98
101
|
'modules': module
|
|
99
102
|
})
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
try:
|
|
105
|
+
spec = importlib.util.find_spec(module)
|
|
106
|
+
except (ImportError, ValueError, AttributeError):
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if spec is None:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
p = None
|
|
113
|
+
if spec.origin is None and spec.submodule_search_locations:
|
|
114
|
+
for location in spec.submodule_search_locations:
|
|
115
|
+
p = pathlib.Path(location)
|
|
116
|
+
if p.exists():
|
|
117
|
+
break
|
|
118
|
+
if not p:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
if spec.origin is None:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
origin = pathlib.Path(spec.origin)
|
|
125
|
+
if origin.name == '__init__.py':
|
|
126
|
+
origin = origin.parent
|
|
127
|
+
|
|
128
|
+
for path in origin.glob('*.py'):
|
|
102
129
|
__import__(f'{module}.{path.stem}')
|
|
103
130
|
|
|
104
131
|
self.after_load_module(i, module)
|
|
@@ -240,14 +267,6 @@ class AppProxy:
|
|
|
240
267
|
loop = asyncio.get_event_loop()
|
|
241
268
|
loop.set_default_executor(concurrent.futures.ThreadPoolExecutor())
|
|
242
269
|
|
|
243
|
-
if self.app.sync_server_url:
|
|
244
|
-
if WebsocketProxy.sync_servers is None:
|
|
245
|
-
WebsocketProxy.sync_servers = (redis.Redis.from_url(self.app.sync_server_url), redis.asyncio.Redis.from_url(self.app.sync_server_url))
|
|
246
|
-
if WebsocketProxy.data_encode is None:
|
|
247
|
-
WebsocketProxy.data_encode = self.app.sync_server_data_encode
|
|
248
|
-
if WebsocketProxy.data_decode is None:
|
|
249
|
-
WebsocketProxy.data_decode = self.app.sync_server_data_decode
|
|
250
|
-
|
|
251
270
|
await self.after_worker_start(is_first)
|
|
252
271
|
await self.app.signal.after_worker_start.async_send(kwargs = {
|
|
253
272
|
'is_first': is_first
|
|
@@ -443,10 +462,21 @@ class AppProxy:
|
|
|
443
462
|
async def after_response(self, response: Response):
|
|
444
463
|
...
|
|
445
464
|
|
|
465
|
+
def _process_init(self, app: 'CheeseAPI' = None):
|
|
466
|
+
if not app:
|
|
467
|
+
app = self.app
|
|
468
|
+
|
|
469
|
+
static.websocket_data_decode = app.sync_server_data_decode
|
|
470
|
+
static.websocket_data_encode = app.sync_server_data_encode
|
|
471
|
+
if app.sync_server_url:
|
|
472
|
+
static.websocket_sync_servers = (redis.ConnectionPool.from_url(app.sync_server_url), redis.asyncio.ConnectionPool.from_url(app.sync_server_url))
|
|
473
|
+
static.websocket_sync_server = {}
|
|
474
|
+
app.scheduler._proxy.init(app)
|
|
475
|
+
|
|
446
476
|
class CheeseAPI:
|
|
447
|
-
__slots__ = ('_host', '_port', '_ipv6', '_logger_path', '_dual_stack', '_socket_backlog', '_socket_send_buffer_size', '_socket_receive_buffer_size', '_workers', '_ssl_cert', '_ssl_key', '_sync_server_url', '_static_path', '_printer', '_compress', '_compress_min_length', '_compress_level', '_manual_modules', '_exclude_modules', '_priority_modules', '_sync_server_data_encode', '_sync_server_data_decode', '_logger_messages', '_logger', '_is_running', '_request_timeout', '_keep_alive', '_keep_alive_timeout', '_keep_alive_max_requests', '_AppProxy_Class', '_RequestProxy_Class', '_proxy', '_signal', '_ResponseProxy_Class', '_RouteProxy_Class', '_route', '_WebsocketProxy_Class', '_cors', '_SchedulerProxy_Class', '_scheduler')
|
|
477
|
+
__slots__ = ('_host', '_port', '_ipv6', '_logger_path', '_dual_stack', '_socket_backlog', '_socket_send_buffer_size', '_socket_receive_buffer_size', '_workers', '_ssl_cert', '_ssl_key', '_sync_server_url', '_static_path', '_printer', '_compress', '_compress_min_length', '_compress_level', '_manual_modules', '_exclude_modules', '_priority_modules', '_sync_server_data_encode', '_sync_server_data_decode', '_logger_messages', '_logger', '_is_running', '_request_timeout', '_keep_alive', '_keep_alive_timeout', '_keep_alive_max_requests', '_AppProxy_Class', '_RequestProxy_Class', '_proxy', '_signal', '_ResponseProxy_Class', '_RouteProxy_Class', '_route', '_WebsocketProxy_Class', '_cors', '_SchedulerProxy_Class', '_scheduler', '_sync_server_timeout')
|
|
448
478
|
|
|
449
|
-
def __init__(self, host: str | None = None, port: int = 5214, *, ipv6: bool = False, logger_path: str | None = None, dual_stack: bool = False, socket_backlog: int | None = None, socket_send_buffer_size: int | None = None, socket_receive_buffer_size: int | None = None, workers: int = 1, ssl_cert: str | None = None, ssl_key: str | None = None, sync_server_url: str | None = None, static_path: dict[str, str] = {}, printer: Type[Printer] = Printer, compress: list[Literal['gzip', 'br', 'zstd', 'deflate']] = ['gzip', 'br', 'zstd', 'deflate'], compress_min_length: int = 1024, compress_level: int = 6, manual_modules: list[str] = [], exclude_modules: list[str] = [], priority_modules: list[str] = [], sync_server_data_encode: Callable[[bytes], bytes] | None = None, sync_server_data_decode: Callable[[bytes], bytes] | None = None, logger_messages: dict[str, 'Message'] = {}, request_timeout: float | None = None, keep_alive: bool = True, keep_alive_timeout: float = 5, keep_alive_max_requests: int = 100, AppProxy_Class: Type[AppProxy] = AppProxy, RequestProxy_Class: Type[RequestProxy] = RequestProxy, ResponseProxy_Class: Type[ResponseProxy] = ResponseProxy, RouteProxy_Class: Type[RouteProxy] = RouteProxy, cors_allow_origins: list[str] = ['*'], cors_allow_methods: Literal['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT'] = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'CONNECT'], cors_allow_headers: list[str] = ['*'], cors_allow_credentials: bool = True, cors_expose_headers: list[str] = [], cors_max_age: int | None = None, WebsocketProxy_Class: Type[WebsocketProxy] = WebsocketProxy, route_patterns: list['Pattern'] = [], SchedulerProxy_Class: Type[SchedulerProxy] = SchedulerProxy):
|
|
479
|
+
def __init__(self, host: str | None = None, port: int = 5214, *, ipv6: bool = False, logger_path: str | None = None, dual_stack: bool = False, socket_backlog: int | None = None, socket_send_buffer_size: int | None = None, socket_receive_buffer_size: int | None = None, workers: int = 1, ssl_cert: str | None = None, ssl_key: str | None = None, sync_server_url: str | None = None, static_path: dict[str, str] = {}, printer: Type[Printer] = Printer, compress: list[Literal['gzip', 'br', 'zstd', 'deflate']] = ['gzip', 'br', 'zstd', 'deflate'], compress_min_length: int = 1024, compress_level: int = 6, manual_modules: list[str] = [], exclude_modules: list[str] = [], priority_modules: list[str] = [], sync_server_data_encode: Callable[[bytes], bytes] | None = None, sync_server_data_decode: Callable[[bytes], bytes] | None = None, logger_messages: dict[str, 'Message'] = {}, request_timeout: float | None = None, keep_alive: bool = True, keep_alive_timeout: float = 5, keep_alive_max_requests: int = 100, AppProxy_Class: Type[AppProxy] = AppProxy, RequestProxy_Class: Type[RequestProxy] = RequestProxy, ResponseProxy_Class: Type[ResponseProxy] = ResponseProxy, RouteProxy_Class: Type[RouteProxy] = RouteProxy, cors_allow_origins: list[str] = ['*'], cors_allow_methods: Literal['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT'] = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'CONNECT'], cors_allow_headers: list[str] = ['*'], cors_allow_credentials: bool = True, cors_expose_headers: list[str] = [], cors_max_age: int | None = None, WebsocketProxy_Class: Type[WebsocketProxy] = WebsocketProxy, route_patterns: list['Pattern'] = [], SchedulerProxy_Class: Type[SchedulerProxy] = SchedulerProxy, sync_server_timeout: float = 5):
|
|
450
480
|
'''
|
|
451
481
|
- Args
|
|
452
482
|
- logger_path: 日志文件路径,支持日期格式化
|
|
@@ -478,6 +508,7 @@ class CheeseAPI:
|
|
|
478
508
|
- WebsocketProxy_Class: 若想要对 Websocket 处理逻辑进行处理,可传入自定义的 WebsocketProxy 类
|
|
479
509
|
- route_patterns: 自定义路由校验规则
|
|
480
510
|
- SchedulerProxy_Class: 自定义任务调度器代理类
|
|
511
|
+
- sync_server_timeout: 同步服务器操作超时时间
|
|
481
512
|
'''
|
|
482
513
|
|
|
483
514
|
self._host: str = host if host is not None else ('::' if ipv6 else '0.0.0.0')
|
|
@@ -513,6 +544,7 @@ class CheeseAPI:
|
|
|
513
544
|
self._RouteProxy_Class: Type[RouteProxy] = RouteProxy_Class
|
|
514
545
|
self._WebsocketProxy_Class: Type[WebsocketProxy] = WebsocketProxy_Class
|
|
515
546
|
self._SchedulerProxy_Class: Type[SchedulerProxy] = SchedulerProxy_Class
|
|
547
|
+
self._sync_server_timeout: float = sync_server_timeout
|
|
516
548
|
|
|
517
549
|
self._logger: CheeseLogger = CheeseLogger(self.logger_path, messages = {
|
|
518
550
|
'START': Message('START', 20, message_template_styled = '(<green>%k</green>) <black>%t</black> > %c'),
|
|
@@ -533,16 +565,10 @@ class CheeseAPI:
|
|
|
533
565
|
self.route.patterns.extend(route_patterns)
|
|
534
566
|
self.route.patterns.sort(key = lambda x: x['weight'], reverse = True)
|
|
535
567
|
|
|
536
|
-
def __setstate__(self,
|
|
537
|
-
for key, value in
|
|
568
|
+
def __setstate__(self, data: tuple[None, dict[str, any]]):
|
|
569
|
+
for key, value in data[1].items():
|
|
538
570
|
setattr(self, key, value)
|
|
539
|
-
|
|
540
|
-
if self.sync_server_url and WebsocketProxy.sync_servers is None:
|
|
541
|
-
WebsocketProxy.sync_servers = (redis.Redis.from_url(self.sync_server_url), redis.asyncio.Redis.from_url(self.sync_server_url))
|
|
542
|
-
if WebsocketProxy.data_encode is None:
|
|
543
|
-
WebsocketProxy.data_encode = self.sync_server_data_encode
|
|
544
|
-
if WebsocketProxy.data_decode is None:
|
|
545
|
-
WebsocketProxy.data_decode = self.sync_server_data_decode
|
|
571
|
+
self._proxy._process_init(self)
|
|
546
572
|
|
|
547
573
|
def start(self):
|
|
548
574
|
self._proxy.start()
|
|
@@ -829,3 +855,11 @@ class CheeseAPI:
|
|
|
829
855
|
'''
|
|
830
856
|
|
|
831
857
|
return self._SchedulerProxy_Class
|
|
858
|
+
|
|
859
|
+
@property
|
|
860
|
+
def sync_server_timeout(self) -> float:
|
|
861
|
+
'''
|
|
862
|
+
同步服务器操作超时时间
|
|
863
|
+
'''
|
|
864
|
+
|
|
865
|
+
return self._sync_server_timeout
|
|
@@ -5,7 +5,7 @@ from CheeseAPI.response import Response
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from CheeseAPI.request import Request
|
|
7
7
|
|
|
8
|
-
HTTP_METHOD_TYPE = Literal['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH']
|
|
8
|
+
HTTP_METHOD_TYPE = Literal['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'CONNECT']
|
|
9
9
|
|
|
10
10
|
class CORS:
|
|
11
11
|
__slots__ = ('allow_origins', 'allow_methods', 'allow_headers', 'allow_credentials', 'expose_headers', 'max_age')
|
|
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|
|
11
11
|
HTTP_METHOD_TYPE = Literal['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE', 'WEBSOCKET']
|
|
12
12
|
|
|
13
13
|
class Request:
|
|
14
|
-
__slots__ = ('_proxy', '_ip', '_method', '_path', '_params', '_headers', '_query', '_body', '_json', '_form', '_files', '_cookies', '_full_path', '_ranges', '_fn')
|
|
14
|
+
__slots__ = ('_proxy', '_ip', '_method', '_path', '_params', '_headers', '_query', '_body', '_json', '_form', '_files', '_cookies', '_full_path', '_ranges', '_fn', '_file')
|
|
15
15
|
|
|
16
16
|
def __init__(self, app: 'CheeseAPI', client_socket: socket.socket, addr: tuple[str, int]):
|
|
17
17
|
self._proxy: RequestProxy = app.RequestProxy_Class(app, self, client_socket)
|
|
@@ -26,6 +26,7 @@ class Request:
|
|
|
26
26
|
self._json: dict | list | None = None
|
|
27
27
|
self._form: dict[str, str] | None = None
|
|
28
28
|
self._files: dict[str, File] | None = None
|
|
29
|
+
self._file: File | None = None
|
|
29
30
|
self._cookies: dict[str, str] | None = None
|
|
30
31
|
self._full_path: str | None = None
|
|
31
32
|
self._ranges: list[tuple[int, int | None]] | None = None
|
|
@@ -93,6 +94,10 @@ class Request:
|
|
|
93
94
|
def files(self) -> dict[str, bytes] | None:
|
|
94
95
|
return self._files
|
|
95
96
|
|
|
97
|
+
@property
|
|
98
|
+
def file(self) -> File | None:
|
|
99
|
+
return self._file
|
|
100
|
+
|
|
96
101
|
@property
|
|
97
102
|
def cookies(self) -> dict[str, str] | None:
|
|
98
103
|
return self._cookies
|
|
@@ -183,6 +188,7 @@ class RequestProxy:
|
|
|
183
188
|
data = None
|
|
184
189
|
try:
|
|
185
190
|
data = await asyncio.wait_for(loop.sock_recv(self.client_socket, self.app.socket_receive_buffer_size), self.app.request_timeout)
|
|
191
|
+
self.request._body += data
|
|
186
192
|
except asyncio.TimeoutError:
|
|
187
193
|
raise
|
|
188
194
|
if not data:
|
|
@@ -75,6 +75,10 @@ HTTP_STATUS = {
|
|
|
75
75
|
}
|
|
76
76
|
NO_BODY_STATUS = (100, 101, 102, 204, 304)
|
|
77
77
|
PREVIEWABLE_TYPES = ('text/plain', 'text/html', 'text/css', 'text/javascript', 'application/json', 'application/xml', 'text/xml', 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp', 'image/bmp', 'video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/webm', 'application/pdf')
|
|
78
|
+
MERGE_TYPES = {
|
|
79
|
+
'audio/x-wav': 'audio/wav',
|
|
80
|
+
'audio/vnd.wave': 'audio/wav'
|
|
81
|
+
}
|
|
78
82
|
|
|
79
83
|
class Cookie(TypedDict):
|
|
80
84
|
value: str
|
|
@@ -87,7 +91,7 @@ class Cookie(TypedDict):
|
|
|
87
91
|
class Response:
|
|
88
92
|
__slots__ = ('status', '_proxy', 'body', 'headers', 'cookies', 'high_precision_date', 'compress', 'compress_level')
|
|
89
93
|
|
|
90
|
-
def __init__(self, body: dict | list | str | bytes | AsyncIterable | None = None, status: int = 200, headers: dict[str, str] =
|
|
94
|
+
def __init__(self, body: dict | list | str | bytes | AsyncIterable | None = None, status: int = 200, headers: dict[str, str] | None = None, *, high_precision_date: bool = False, compress: Literal['gzip', 'deflate', 'br', 'zstd'] | None = None, compress_level: int | None = None):
|
|
91
95
|
'''
|
|
92
96
|
- Args
|
|
93
97
|
- body: 当为 `AsyncIterable` 时,自动使用 chunked 传输编码
|
|
@@ -99,7 +103,7 @@ class Response:
|
|
|
99
103
|
|
|
100
104
|
self.status: int = status
|
|
101
105
|
self.body: dict | list | str | bytes | AsyncIterable | None = body
|
|
102
|
-
self.headers: dict[str, str] = headers
|
|
106
|
+
self.headers: dict[str, str] = headers if headers is not None else {}
|
|
103
107
|
self.cookies: dict[str, Cookie] = {}
|
|
104
108
|
self.high_precision_date: bool = high_precision_date
|
|
105
109
|
self.compress: Literal['gzip', 'deflate', 'br', 'zstd'] | None = compress
|
|
@@ -118,7 +122,9 @@ class Response:
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
class RedirectResponse(Response):
|
|
121
|
-
def __init__(self, location: str, status: Literal[301, 302, 303, 307, 308] = 302, headers: dict[str, str] =
|
|
125
|
+
def __init__(self, location: str, status: Literal[301, 302, 303, 307, 308] = 302, headers: dict[str, str] | None = None, body: bytes | str | list | dict | None = None):
|
|
126
|
+
if headers is None:
|
|
127
|
+
headers = {}
|
|
122
128
|
headers['Location'] = location
|
|
123
129
|
|
|
124
130
|
super().__init__(status, body, headers)
|
|
@@ -126,7 +132,7 @@ class RedirectResponse(Response):
|
|
|
126
132
|
class FileResponse(Response):
|
|
127
133
|
__slots__ = ('file', 'preview', 'transmission_type', 'chunked_size')
|
|
128
134
|
|
|
129
|
-
def __init__(self, file_path_or_file: str | File, *, status: int = 200, headers: dict[str, str] =
|
|
135
|
+
def __init__(self, file_path_or_file: str | File, *, status: int = 200, headers: dict[str, str] | None = None , preview: bool = True, transmission_type: Literal['CONTENT_LENGTH', 'CHUNKED'] = 'CONTENT_LENGTH', chunked_size: int | None = None, compress: Literal['gzip', 'deflate', 'br', 'zstd'] | None = None, compress_level: int | None = None):
|
|
130
136
|
'''
|
|
131
137
|
- Args
|
|
132
138
|
- preview: 优先预览文件
|
|
@@ -225,6 +231,8 @@ class ResponseProxy:
|
|
|
225
231
|
|
|
226
232
|
if 'Content-Type' not in headers and 'Content-Disposition' not in headers:
|
|
227
233
|
mime_type = mimetypes.guess_type(self.response.file.name)[0] or 'application/octet-stream'
|
|
234
|
+
if mime_type in MERGE_TYPES:
|
|
235
|
+
mime_type = MERGE_TYPES[mime_type]
|
|
228
236
|
headers['Content-Type'] = f'{mime_type}; charset=utf-8'
|
|
229
237
|
headers['Content-Disposition'] = f'{"inline" if self.response.preview and mime_type in PREVIEWABLE_TYPES else "attachment"}; filename="{self.response.file.name}"'
|
|
230
238
|
|
|
@@ -398,4 +406,4 @@ class ResponseProxy:
|
|
|
398
406
|
if 'Content-Encoding' in headers:
|
|
399
407
|
del headers['Content-Encoding']
|
|
400
408
|
|
|
401
|
-
return status, headers, body
|
|
409
|
+
return status, headers, body
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import re, uuid
|
|
2
2
|
from typing import Literal, TypedDict, Callable, TYPE_CHECKING, AsyncIterable, Union
|
|
3
3
|
|
|
4
|
+
from urllib.parse import unquote
|
|
5
|
+
|
|
4
6
|
from CheeseAPI.cors import CORS
|
|
5
7
|
|
|
6
8
|
if TYPE_CHECKING:
|
|
@@ -252,7 +254,7 @@ class RouteProxy:
|
|
|
252
254
|
i = 1
|
|
253
255
|
for key, type in self.app.route._proxy.dynamic_routes[_path][method]['params'].items():
|
|
254
256
|
value = match.group(i)
|
|
255
|
-
_params[key] = type(value)
|
|
257
|
+
_params[key] = type(unquote(value))
|
|
256
258
|
i += 1
|
|
257
259
|
return self.app.route._proxy.dynamic_routes[_path][method], _params
|
|
258
260
|
else:
|