koichi12 commited on
Commit
15cdb41
·
verified ·
1 Parent(s): 68d3336

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. .venv/lib/python3.11/site-packages/rpds/rpds.cpython-311-x86_64-linux-gnu.so +3 -0
  3. .venv/lib/python3.11/site-packages/starlette/__init__.py +1 -0
  4. .venv/lib/python3.11/site-packages/starlette/__pycache__/__init__.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/starlette/__pycache__/_exception_handler.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/starlette/__pycache__/_utils.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/starlette/__pycache__/applications.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/starlette/__pycache__/authentication.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/starlette/__pycache__/background.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/starlette/__pycache__/concurrency.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/starlette/__pycache__/config.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/starlette/__pycache__/convertors.cpython-311.pyc +0 -0
  13. .venv/lib/python3.11/site-packages/starlette/__pycache__/datastructures.cpython-311.pyc +0 -0
  14. .venv/lib/python3.11/site-packages/starlette/__pycache__/endpoints.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/starlette/__pycache__/exceptions.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/starlette/__pycache__/formparsers.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/starlette/__pycache__/requests.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/starlette/__pycache__/responses.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/starlette/__pycache__/routing.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/starlette/__pycache__/schemas.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/starlette/__pycache__/staticfiles.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/starlette/__pycache__/status.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/starlette/__pycache__/templating.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/starlette/__pycache__/testclient.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/starlette/__pycache__/types.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/starlette/__pycache__/websockets.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/starlette/_utils.py +100 -0
  28. .venv/lib/python3.11/site-packages/starlette/authentication.py +147 -0
  29. .venv/lib/python3.11/site-packages/starlette/background.py +41 -0
  30. .venv/lib/python3.11/site-packages/starlette/endpoints.py +122 -0
  31. .venv/lib/python3.11/site-packages/starlette/formparsers.py +272 -0
  32. .venv/lib/python3.11/site-packages/starlette/middleware/__init__.py +42 -0
  33. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/__init__.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/authentication.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/base.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/cors.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/errors.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/exceptions.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/gzip.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/httpsredirect.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/sessions.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/trustedhost.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/wsgi.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/starlette/middleware/authentication.py +52 -0
  45. .venv/lib/python3.11/site-packages/starlette/middleware/base.py +221 -0
  46. .venv/lib/python3.11/site-packages/starlette/middleware/cors.py +172 -0
  47. .venv/lib/python3.11/site-packages/starlette/middleware/errors.py +260 -0
  48. .venv/lib/python3.11/site-packages/starlette/middleware/exceptions.py +72 -0
  49. .venv/lib/python3.11/site-packages/starlette/middleware/gzip.py +108 -0
  50. .venv/lib/python3.11/site-packages/starlette/middleware/httpsredirect.py +19 -0
.gitattributes CHANGED
@@ -207,3 +207,4 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_
207
  .venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
208
  .venv/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
209
  .venv/lib/python3.11/site-packages/__pycache__/pynvml.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
 
 
207
  .venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
208
  .venv/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
209
  .venv/lib/python3.11/site-packages/__pycache__/pynvml.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
210
+ .venv/lib/python3.11/site-packages/rpds/rpds.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/site-packages/rpds/rpds.cpython-311-x86_64-linux-gnu.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aded6ee5bd881096565cbd54a06c9cb432b1ec9e69b4fdc29f37f32c4573be16
3
+ size 1015312
.venv/lib/python3.11/site-packages/starlette/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "0.45.3"
.venv/lib/python3.11/site-packages/starlette/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (203 Bytes). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/_exception_handler.cpython-311.pyc ADDED
Binary file (3.61 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/_utils.cpython-311.pyc ADDED
Binary file (5.87 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/applications.cpython-311.pyc ADDED
Binary file (13.8 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/authentication.cpython-311.pyc ADDED
Binary file (9.21 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/background.cpython-311.pyc ADDED
Binary file (2.95 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/concurrency.cpython-311.pyc ADDED
Binary file (3.7 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/config.cpython-311.pyc ADDED
Binary file (8.26 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/convertors.cpython-311.pyc ADDED
Binary file (5.77 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/datastructures.cpython-311.pyc ADDED
Binary file (45.7 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/endpoints.cpython-311.pyc ADDED
Binary file (8.7 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/exceptions.cpython-311.pyc ADDED
Binary file (2.61 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/formparsers.cpython-311.pyc ADDED
Binary file (15 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/requests.cpython-311.pyc ADDED
Binary file (17.8 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/responses.cpython-311.pyc ADDED
Binary file (32.3 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/routing.cpython-311.pyc ADDED
Binary file (48.5 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (8.04 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/staticfiles.cpython-311.pyc ADDED
Binary file (12.6 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/status.cpython-311.pyc ADDED
Binary file (3.65 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/templating.cpython-311.pyc ADDED
Binary file (11.1 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/testclient.cpython-311.pyc ADDED
Binary file (35.5 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/types.cpython-311.pyc ADDED
Binary file (1.68 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/__pycache__/websockets.cpython-311.pyc ADDED
Binary file (13 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/_utils.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import functools
5
+ import sys
6
+ import typing
7
+ from contextlib import contextmanager
8
+
9
+ from starlette.types import Scope
10
+
11
+ if sys.version_info >= (3, 10): # pragma: no cover
12
+ from typing import TypeGuard
13
+ else: # pragma: no cover
14
+ from typing_extensions import TypeGuard
15
+
16
+ has_exceptiongroups = True
17
+ if sys.version_info < (3, 11): # pragma: no cover
18
+ try:
19
+ from exceptiongroup import BaseExceptionGroup # type: ignore[unused-ignore,import-not-found]
20
+ except ImportError:
21
+ has_exceptiongroups = False
22
+
23
+ T = typing.TypeVar("T")
24
+ AwaitableCallable = typing.Callable[..., typing.Awaitable[T]]
25
+
26
+
27
+ @typing.overload
28
+ def is_async_callable(obj: AwaitableCallable[T]) -> TypeGuard[AwaitableCallable[T]]: ...
29
+
30
+
31
+ @typing.overload
32
+ def is_async_callable(obj: typing.Any) -> TypeGuard[AwaitableCallable[typing.Any]]: ...
33
+
34
+
35
+ def is_async_callable(obj: typing.Any) -> typing.Any:
36
+ while isinstance(obj, functools.partial):
37
+ obj = obj.func
38
+
39
+ return asyncio.iscoroutinefunction(obj) or (callable(obj) and asyncio.iscoroutinefunction(obj.__call__))
40
+
41
+
42
+ T_co = typing.TypeVar("T_co", covariant=True)
43
+
44
+
45
+ class AwaitableOrContextManager(typing.Awaitable[T_co], typing.AsyncContextManager[T_co], typing.Protocol[T_co]): ...
46
+
47
+
48
+ class SupportsAsyncClose(typing.Protocol):
49
+ async def close(self) -> None: ... # pragma: no cover
50
+
51
+
52
+ SupportsAsyncCloseType = typing.TypeVar("SupportsAsyncCloseType", bound=SupportsAsyncClose, covariant=False)
53
+
54
+
55
+ class AwaitableOrContextManagerWrapper(typing.Generic[SupportsAsyncCloseType]):
56
+ __slots__ = ("aw", "entered")
57
+
58
+ def __init__(self, aw: typing.Awaitable[SupportsAsyncCloseType]) -> None:
59
+ self.aw = aw
60
+
61
+ def __await__(self) -> typing.Generator[typing.Any, None, SupportsAsyncCloseType]:
62
+ return self.aw.__await__()
63
+
64
+ async def __aenter__(self) -> SupportsAsyncCloseType:
65
+ self.entered = await self.aw
66
+ return self.entered
67
+
68
+ async def __aexit__(self, *args: typing.Any) -> None | bool:
69
+ await self.entered.close()
70
+ return None
71
+
72
+
73
+ @contextmanager
74
+ def collapse_excgroups() -> typing.Generator[None, None, None]:
75
+ try:
76
+ yield
77
+ except BaseException as exc:
78
+ if has_exceptiongroups: # pragma: no cover
79
+ while isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1:
80
+ exc = exc.exceptions[0]
81
+
82
+ raise exc
83
+
84
+
85
+ def get_route_path(scope: Scope) -> str:
86
+ path: str = scope["path"]
87
+ root_path = scope.get("root_path", "")
88
+ if not root_path:
89
+ return path
90
+
91
+ if not path.startswith(root_path):
92
+ return path
93
+
94
+ if path == root_path:
95
+ return ""
96
+
97
+ if path[len(root_path)] == "/":
98
+ return path[len(root_path) :]
99
+
100
+ return path
.venv/lib/python3.11/site-packages/starlette/authentication.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import inspect
5
+ import sys
6
+ import typing
7
+ from urllib.parse import urlencode
8
+
9
+ if sys.version_info >= (3, 10): # pragma: no cover
10
+ from typing import ParamSpec
11
+ else: # pragma: no cover
12
+ from typing_extensions import ParamSpec
13
+
14
+ from starlette._utils import is_async_callable
15
+ from starlette.exceptions import HTTPException
16
+ from starlette.requests import HTTPConnection, Request
17
+ from starlette.responses import RedirectResponse
18
+ from starlette.websockets import WebSocket
19
+
20
+ _P = ParamSpec("_P")
21
+
22
+
23
+ def has_required_scope(conn: HTTPConnection, scopes: typing.Sequence[str]) -> bool:
24
+ for scope in scopes:
25
+ if scope not in conn.auth.scopes:
26
+ return False
27
+ return True
28
+
29
+
30
+ def requires(
31
+ scopes: str | typing.Sequence[str],
32
+ status_code: int = 403,
33
+ redirect: str | None = None,
34
+ ) -> typing.Callable[[typing.Callable[_P, typing.Any]], typing.Callable[_P, typing.Any]]:
35
+ scopes_list = [scopes] if isinstance(scopes, str) else list(scopes)
36
+
37
+ def decorator(
38
+ func: typing.Callable[_P, typing.Any],
39
+ ) -> typing.Callable[_P, typing.Any]:
40
+ sig = inspect.signature(func)
41
+ for idx, parameter in enumerate(sig.parameters.values()):
42
+ if parameter.name == "request" or parameter.name == "websocket":
43
+ type_ = parameter.name
44
+ break
45
+ else:
46
+ raise Exception(f'No "request" or "websocket" argument on function "{func}"')
47
+
48
+ if type_ == "websocket":
49
+ # Handle websocket functions. (Always async)
50
+ @functools.wraps(func)
51
+ async def websocket_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
52
+ websocket = kwargs.get("websocket", args[idx] if idx < len(args) else None)
53
+ assert isinstance(websocket, WebSocket)
54
+
55
+ if not has_required_scope(websocket, scopes_list):
56
+ await websocket.close()
57
+ else:
58
+ await func(*args, **kwargs)
59
+
60
+ return websocket_wrapper
61
+
62
+ elif is_async_callable(func):
63
+ # Handle async request/response functions.
64
+ @functools.wraps(func)
65
+ async def async_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> typing.Any:
66
+ request = kwargs.get("request", args[idx] if idx < len(args) else None)
67
+ assert isinstance(request, Request)
68
+
69
+ if not has_required_scope(request, scopes_list):
70
+ if redirect is not None:
71
+ orig_request_qparam = urlencode({"next": str(request.url)})
72
+ next_url = f"{request.url_for(redirect)}?{orig_request_qparam}"
73
+ return RedirectResponse(url=next_url, status_code=303)
74
+ raise HTTPException(status_code=status_code)
75
+ return await func(*args, **kwargs)
76
+
77
+ return async_wrapper
78
+
79
+ else:
80
+ # Handle sync request/response functions.
81
+ @functools.wraps(func)
82
+ def sync_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> typing.Any:
83
+ request = kwargs.get("request", args[idx] if idx < len(args) else None)
84
+ assert isinstance(request, Request)
85
+
86
+ if not has_required_scope(request, scopes_list):
87
+ if redirect is not None:
88
+ orig_request_qparam = urlencode({"next": str(request.url)})
89
+ next_url = f"{request.url_for(redirect)}?{orig_request_qparam}"
90
+ return RedirectResponse(url=next_url, status_code=303)
91
+ raise HTTPException(status_code=status_code)
92
+ return func(*args, **kwargs)
93
+
94
+ return sync_wrapper
95
+
96
+ return decorator
97
+
98
+
99
+ class AuthenticationError(Exception):
100
+ pass
101
+
102
+
103
+ class AuthenticationBackend:
104
+ async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, BaseUser] | None:
105
+ raise NotImplementedError() # pragma: no cover
106
+
107
+
108
+ class AuthCredentials:
109
+ def __init__(self, scopes: typing.Sequence[str] | None = None):
110
+ self.scopes = [] if scopes is None else list(scopes)
111
+
112
+
113
+ class BaseUser:
114
+ @property
115
+ def is_authenticated(self) -> bool:
116
+ raise NotImplementedError() # pragma: no cover
117
+
118
+ @property
119
+ def display_name(self) -> str:
120
+ raise NotImplementedError() # pragma: no cover
121
+
122
+ @property
123
+ def identity(self) -> str:
124
+ raise NotImplementedError() # pragma: no cover
125
+
126
+
127
+ class SimpleUser(BaseUser):
128
+ def __init__(self, username: str) -> None:
129
+ self.username = username
130
+
131
+ @property
132
+ def is_authenticated(self) -> bool:
133
+ return True
134
+
135
+ @property
136
+ def display_name(self) -> str:
137
+ return self.username
138
+
139
+
140
+ class UnauthenticatedUser(BaseUser):
141
+ @property
142
+ def is_authenticated(self) -> bool:
143
+ return False
144
+
145
+ @property
146
+ def display_name(self) -> str:
147
+ return ""
.venv/lib/python3.11/site-packages/starlette/background.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import typing
5
+
6
+ if sys.version_info >= (3, 10): # pragma: no cover
7
+ from typing import ParamSpec
8
+ else: # pragma: no cover
9
+ from typing_extensions import ParamSpec
10
+
11
+ from starlette._utils import is_async_callable
12
+ from starlette.concurrency import run_in_threadpool
13
+
14
+ P = ParamSpec("P")
15
+
16
+
17
+ class BackgroundTask:
18
+ def __init__(self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs) -> None:
19
+ self.func = func
20
+ self.args = args
21
+ self.kwargs = kwargs
22
+ self.is_async = is_async_callable(func)
23
+
24
+ async def __call__(self) -> None:
25
+ if self.is_async:
26
+ await self.func(*self.args, **self.kwargs)
27
+ else:
28
+ await run_in_threadpool(self.func, *self.args, **self.kwargs)
29
+
30
+
31
+ class BackgroundTasks(BackgroundTask):
32
+ def __init__(self, tasks: typing.Sequence[BackgroundTask] | None = None):
33
+ self.tasks = list(tasks) if tasks else []
34
+
35
+ def add_task(self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs) -> None:
36
+ task = BackgroundTask(func, *args, **kwargs)
37
+ self.tasks.append(task)
38
+
39
+ async def __call__(self) -> None:
40
+ for task in self.tasks:
41
+ await task()
.venv/lib/python3.11/site-packages/starlette/endpoints.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import typing
5
+
6
+ from starlette import status
7
+ from starlette._utils import is_async_callable
8
+ from starlette.concurrency import run_in_threadpool
9
+ from starlette.exceptions import HTTPException
10
+ from starlette.requests import Request
11
+ from starlette.responses import PlainTextResponse, Response
12
+ from starlette.types import Message, Receive, Scope, Send
13
+ from starlette.websockets import WebSocket
14
+
15
+
16
+ class HTTPEndpoint:
17
+ def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
18
+ assert scope["type"] == "http"
19
+ self.scope = scope
20
+ self.receive = receive
21
+ self.send = send
22
+ self._allowed_methods = [
23
+ method
24
+ for method in ("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
25
+ if getattr(self, method.lower(), None) is not None
26
+ ]
27
+
28
+ def __await__(self) -> typing.Generator[typing.Any, None, None]:
29
+ return self.dispatch().__await__()
30
+
31
+ async def dispatch(self) -> None:
32
+ request = Request(self.scope, receive=self.receive)
33
+ handler_name = "get" if request.method == "HEAD" and not hasattr(self, "head") else request.method.lower()
34
+
35
+ handler: typing.Callable[[Request], typing.Any] = getattr(self, handler_name, self.method_not_allowed)
36
+ is_async = is_async_callable(handler)
37
+ if is_async:
38
+ response = await handler(request)
39
+ else:
40
+ response = await run_in_threadpool(handler, request)
41
+ await response(self.scope, self.receive, self.send)
42
+
43
+ async def method_not_allowed(self, request: Request) -> Response:
44
+ # If we're running inside a starlette application then raise an
45
+ # exception, so that the configurable exception handler can deal with
46
+ # returning the response. For plain ASGI apps, just return the response.
47
+ headers = {"Allow": ", ".join(self._allowed_methods)}
48
+ if "app" in self.scope:
49
+ raise HTTPException(status_code=405, headers=headers)
50
+ return PlainTextResponse("Method Not Allowed", status_code=405, headers=headers)
51
+
52
+
53
+ class WebSocketEndpoint:
54
+ encoding: str | None = None # May be "text", "bytes", or "json".
55
+
56
+ def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
57
+ assert scope["type"] == "websocket"
58
+ self.scope = scope
59
+ self.receive = receive
60
+ self.send = send
61
+
62
+ def __await__(self) -> typing.Generator[typing.Any, None, None]:
63
+ return self.dispatch().__await__()
64
+
65
+ async def dispatch(self) -> None:
66
+ websocket = WebSocket(self.scope, receive=self.receive, send=self.send)
67
+ await self.on_connect(websocket)
68
+
69
+ close_code = status.WS_1000_NORMAL_CLOSURE
70
+
71
+ try:
72
+ while True:
73
+ message = await websocket.receive()
74
+ if message["type"] == "websocket.receive":
75
+ data = await self.decode(websocket, message)
76
+ await self.on_receive(websocket, data)
77
+ elif message["type"] == "websocket.disconnect": # pragma: no branch
78
+ close_code = int(message.get("code") or status.WS_1000_NORMAL_CLOSURE)
79
+ break
80
+ except Exception as exc:
81
+ close_code = status.WS_1011_INTERNAL_ERROR
82
+ raise exc
83
+ finally:
84
+ await self.on_disconnect(websocket, close_code)
85
+
86
+ async def decode(self, websocket: WebSocket, message: Message) -> typing.Any:
87
+ if self.encoding == "text":
88
+ if "text" not in message:
89
+ await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
90
+ raise RuntimeError("Expected text websocket messages, but got bytes")
91
+ return message["text"]
92
+
93
+ elif self.encoding == "bytes":
94
+ if "bytes" not in message:
95
+ await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
96
+ raise RuntimeError("Expected bytes websocket messages, but got text")
97
+ return message["bytes"]
98
+
99
+ elif self.encoding == "json":
100
+ if message.get("text") is not None:
101
+ text = message["text"]
102
+ else:
103
+ text = message["bytes"].decode("utf-8")
104
+
105
+ try:
106
+ return json.loads(text)
107
+ except json.decoder.JSONDecodeError:
108
+ await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
109
+ raise RuntimeError("Malformed JSON data received.")
110
+
111
+ assert self.encoding is None, f"Unsupported 'encoding' attribute {self.encoding}"
112
+ return message["text"] if message.get("text") else message["bytes"]
113
+
114
+ async def on_connect(self, websocket: WebSocket) -> None:
115
+ """Override to handle an incoming websocket connection"""
116
+ await websocket.accept()
117
+
118
+ async def on_receive(self, websocket: WebSocket, data: typing.Any) -> None:
119
+ """Override to handle an incoming websocket message"""
120
+
121
+ async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:
122
+ """Override to handle a disconnecting websocket"""
.venv/lib/python3.11/site-packages/starlette/formparsers.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from dataclasses import dataclass, field
5
+ from enum import Enum
6
+ from tempfile import SpooledTemporaryFile
7
+ from urllib.parse import unquote_plus
8
+
9
+ from starlette.datastructures import FormData, Headers, UploadFile
10
+
11
+ if typing.TYPE_CHECKING:
12
+ import python_multipart as multipart
13
+ from python_multipart.multipart import MultipartCallbacks, QuerystringCallbacks, parse_options_header
14
+ else:
15
+ try:
16
+ try:
17
+ import python_multipart as multipart
18
+ from python_multipart.multipart import parse_options_header
19
+ except ModuleNotFoundError: # pragma: no cover
20
+ import multipart
21
+ from multipart.multipart import parse_options_header
22
+ except ModuleNotFoundError: # pragma: no cover
23
+ multipart = None
24
+ parse_options_header = None
25
+
26
+
27
+ class FormMessage(Enum):
28
+ FIELD_START = 1
29
+ FIELD_NAME = 2
30
+ FIELD_DATA = 3
31
+ FIELD_END = 4
32
+ END = 5
33
+
34
+
35
+ @dataclass
36
+ class MultipartPart:
37
+ content_disposition: bytes | None = None
38
+ field_name: str = ""
39
+ data: bytearray = field(default_factory=bytearray)
40
+ file: UploadFile | None = None
41
+ item_headers: list[tuple[bytes, bytes]] = field(default_factory=list)
42
+
43
+
44
+ def _user_safe_decode(src: bytes | bytearray, codec: str) -> str:
45
+ try:
46
+ return src.decode(codec)
47
+ except (UnicodeDecodeError, LookupError):
48
+ return src.decode("latin-1")
49
+
50
+
51
+ class MultiPartException(Exception):
52
+ def __init__(self, message: str) -> None:
53
+ self.message = message
54
+
55
+
56
+ class FormParser:
57
+ def __init__(self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]) -> None:
58
+ assert multipart is not None, "The `python-multipart` library must be installed to use form parsing."
59
+ self.headers = headers
60
+ self.stream = stream
61
+ self.messages: list[tuple[FormMessage, bytes]] = []
62
+
63
+ def on_field_start(self) -> None:
64
+ message = (FormMessage.FIELD_START, b"")
65
+ self.messages.append(message)
66
+
67
+ def on_field_name(self, data: bytes, start: int, end: int) -> None:
68
+ message = (FormMessage.FIELD_NAME, data[start:end])
69
+ self.messages.append(message)
70
+
71
+ def on_field_data(self, data: bytes, start: int, end: int) -> None:
72
+ message = (FormMessage.FIELD_DATA, data[start:end])
73
+ self.messages.append(message)
74
+
75
+ def on_field_end(self) -> None:
76
+ message = (FormMessage.FIELD_END, b"")
77
+ self.messages.append(message)
78
+
79
+ def on_end(self) -> None:
80
+ message = (FormMessage.END, b"")
81
+ self.messages.append(message)
82
+
83
+ async def parse(self) -> FormData:
84
+ # Callbacks dictionary.
85
+ callbacks: QuerystringCallbacks = {
86
+ "on_field_start": self.on_field_start,
87
+ "on_field_name": self.on_field_name,
88
+ "on_field_data": self.on_field_data,
89
+ "on_field_end": self.on_field_end,
90
+ "on_end": self.on_end,
91
+ }
92
+
93
+ # Create the parser.
94
+ parser = multipart.QuerystringParser(callbacks)
95
+ field_name = b""
96
+ field_value = b""
97
+
98
+ items: list[tuple[str, str | UploadFile]] = []
99
+
100
+ # Feed the parser with data from the request.
101
+ async for chunk in self.stream:
102
+ if chunk:
103
+ parser.write(chunk)
104
+ else:
105
+ parser.finalize()
106
+ messages = list(self.messages)
107
+ self.messages.clear()
108
+ for message_type, message_bytes in messages:
109
+ if message_type == FormMessage.FIELD_START:
110
+ field_name = b""
111
+ field_value = b""
112
+ elif message_type == FormMessage.FIELD_NAME:
113
+ field_name += message_bytes
114
+ elif message_type == FormMessage.FIELD_DATA:
115
+ field_value += message_bytes
116
+ elif message_type == FormMessage.FIELD_END:
117
+ name = unquote_plus(field_name.decode("latin-1"))
118
+ value = unquote_plus(field_value.decode("latin-1"))
119
+ items.append((name, value))
120
+
121
+ return FormData(items)
122
+
123
+
124
+ class MultiPartParser:
125
+ max_file_size = 1024 * 1024 # 1MB
126
+
127
+ def __init__(
128
+ self,
129
+ headers: Headers,
130
+ stream: typing.AsyncGenerator[bytes, None],
131
+ *,
132
+ max_files: int | float = 1000,
133
+ max_fields: int | float = 1000,
134
+ max_part_size: int = 1024 * 1024, # 1MB
135
+ ) -> None:
136
+ assert multipart is not None, "The `python-multipart` library must be installed to use form parsing."
137
+ self.headers = headers
138
+ self.stream = stream
139
+ self.max_files = max_files
140
+ self.max_fields = max_fields
141
+ self.items: list[tuple[str, str | UploadFile]] = []
142
+ self._current_files = 0
143
+ self._current_fields = 0
144
+ self._current_partial_header_name: bytes = b""
145
+ self._current_partial_header_value: bytes = b""
146
+ self._current_part = MultipartPart()
147
+ self._charset = ""
148
+ self._file_parts_to_write: list[tuple[MultipartPart, bytes]] = []
149
+ self._file_parts_to_finish: list[MultipartPart] = []
150
+ self._files_to_close_on_error: list[SpooledTemporaryFile[bytes]] = []
151
+ self.max_part_size = max_part_size
152
+
153
+ def on_part_begin(self) -> None:
154
+ self._current_part = MultipartPart()
155
+
156
+ def on_part_data(self, data: bytes, start: int, end: int) -> None:
157
+ message_bytes = data[start:end]
158
+ if self._current_part.file is None:
159
+ if len(self._current_part.data) + len(message_bytes) > self.max_part_size:
160
+ raise MultiPartException(f"Part exceeded maximum size of {int(self.max_part_size / 1024)}KB.")
161
+ self._current_part.data.extend(message_bytes)
162
+ else:
163
+ self._file_parts_to_write.append((self._current_part, message_bytes))
164
+
165
+ def on_part_end(self) -> None:
166
+ if self._current_part.file is None:
167
+ self.items.append(
168
+ (
169
+ self._current_part.field_name,
170
+ _user_safe_decode(self._current_part.data, self._charset),
171
+ )
172
+ )
173
+ else:
174
+ self._file_parts_to_finish.append(self._current_part)
175
+ # The file can be added to the items right now even though it's not
176
+ # finished yet, because it will be finished in the `parse()` method, before
177
+ # self.items is used in the return value.
178
+ self.items.append((self._current_part.field_name, self._current_part.file))
179
+
180
+ def on_header_field(self, data: bytes, start: int, end: int) -> None:
181
+ self._current_partial_header_name += data[start:end]
182
+
183
+ def on_header_value(self, data: bytes, start: int, end: int) -> None:
184
+ self._current_partial_header_value += data[start:end]
185
+
186
+ def on_header_end(self) -> None:
187
+ field = self._current_partial_header_name.lower()
188
+ if field == b"content-disposition":
189
+ self._current_part.content_disposition = self._current_partial_header_value
190
+ self._current_part.item_headers.append((field, self._current_partial_header_value))
191
+ self._current_partial_header_name = b""
192
+ self._current_partial_header_value = b""
193
+
194
+ def on_headers_finished(self) -> None:
195
+ disposition, options = parse_options_header(self._current_part.content_disposition)
196
+ try:
197
+ self._current_part.field_name = _user_safe_decode(options[b"name"], self._charset)
198
+ except KeyError:
199
+ raise MultiPartException('The Content-Disposition header field "name" must be provided.')
200
+ if b"filename" in options:
201
+ self._current_files += 1
202
+ if self._current_files > self.max_files:
203
+ raise MultiPartException(f"Too many files. Maximum number of files is {self.max_files}.")
204
+ filename = _user_safe_decode(options[b"filename"], self._charset)
205
+ tempfile = SpooledTemporaryFile(max_size=self.max_file_size)
206
+ self._files_to_close_on_error.append(tempfile)
207
+ self._current_part.file = UploadFile(
208
+ file=tempfile, # type: ignore[arg-type]
209
+ size=0,
210
+ filename=filename,
211
+ headers=Headers(raw=self._current_part.item_headers),
212
+ )
213
+ else:
214
+ self._current_fields += 1
215
+ if self._current_fields > self.max_fields:
216
+ raise MultiPartException(f"Too many fields. Maximum number of fields is {self.max_fields}.")
217
+ self._current_part.file = None
218
+
219
+ def on_end(self) -> None:
220
+ pass
221
+
222
+ async def parse(self) -> FormData:
223
+ # Parse the Content-Type header to get the multipart boundary.
224
+ _, params = parse_options_header(self.headers["Content-Type"])
225
+ charset = params.get(b"charset", "utf-8")
226
+ if isinstance(charset, bytes):
227
+ charset = charset.decode("latin-1")
228
+ self._charset = charset
229
+ try:
230
+ boundary = params[b"boundary"]
231
+ except KeyError:
232
+ raise MultiPartException("Missing boundary in multipart.")
233
+
234
+ # Callbacks dictionary.
235
+ callbacks: MultipartCallbacks = {
236
+ "on_part_begin": self.on_part_begin,
237
+ "on_part_data": self.on_part_data,
238
+ "on_part_end": self.on_part_end,
239
+ "on_header_field": self.on_header_field,
240
+ "on_header_value": self.on_header_value,
241
+ "on_header_end": self.on_header_end,
242
+ "on_headers_finished": self.on_headers_finished,
243
+ "on_end": self.on_end,
244
+ }
245
+
246
+ # Create the parser.
247
+ parser = multipart.MultipartParser(boundary, callbacks)
248
+ try:
249
+ # Feed the parser with data from the request.
250
+ async for chunk in self.stream:
251
+ parser.write(chunk)
252
+ # Write file data, it needs to use await with the UploadFile methods
253
+ # that call the corresponding file methods *in a threadpool*,
254
+ # otherwise, if they were called directly in the callback methods above
255
+ # (regular, non-async functions), that would block the event loop in
256
+ # the main thread.
257
+ for part, data in self._file_parts_to_write:
258
+ assert part.file # for type checkers
259
+ await part.file.write(data)
260
+ for part in self._file_parts_to_finish:
261
+ assert part.file # for type checkers
262
+ await part.file.seek(0)
263
+ self._file_parts_to_write.clear()
264
+ self._file_parts_to_finish.clear()
265
+ except MultiPartException as exc:
266
+ # Close all the files if there was an error.
267
+ for file in self._files_to_close_on_error:
268
+ file.close()
269
+ raise exc
270
+
271
+ parser.finalize()
272
+ return FormData(self.items)
.venv/lib/python3.11/site-packages/starlette/middleware/__init__.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from collections.abc import Iterator
5
+ from typing import Any, Protocol
6
+
7
+ if sys.version_info >= (3, 10): # pragma: no cover
8
+ from typing import ParamSpec
9
+ else: # pragma: no cover
10
+ from typing_extensions import ParamSpec
11
+
12
+ from starlette.types import ASGIApp
13
+
14
+ P = ParamSpec("P")
15
+
16
+
17
+ class _MiddlewareFactory(Protocol[P]):
18
+ def __call__(self, app: ASGIApp, /, *args: P.args, **kwargs: P.kwargs) -> ASGIApp: ... # pragma: no cover
19
+
20
+
21
+ class Middleware:
22
+ def __init__(
23
+ self,
24
+ cls: _MiddlewareFactory[P],
25
+ *args: P.args,
26
+ **kwargs: P.kwargs,
27
+ ) -> None:
28
+ self.cls = cls
29
+ self.args = args
30
+ self.kwargs = kwargs
31
+
32
+ def __iter__(self) -> Iterator[Any]:
33
+ as_tuple = (self.cls, self.args, self.kwargs)
34
+ return iter(as_tuple)
35
+
36
+ def __repr__(self) -> str:
37
+ class_name = self.__class__.__name__
38
+ args_strings = [f"{value!r}" for value in self.args]
39
+ option_strings = [f"{key}={value!r}" for key, value in self.kwargs.items()]
40
+ name = getattr(self.cls, "__name__", "")
41
+ args_repr = ", ".join([name] + args_strings + option_strings)
42
+ return f"{class_name}({args_repr})"
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (3.08 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/authentication.cpython-311.pyc ADDED
Binary file (3.25 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/base.cpython-311.pyc ADDED
Binary file (12.7 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/cors.cpython-311.pyc ADDED
Binary file (8.45 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/errors.cpython-311.pyc ADDED
Binary file (10.7 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/exceptions.cpython-311.pyc ADDED
Binary file (4.47 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/gzip.cpython-311.pyc ADDED
Binary file (7.38 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/httpsredirect.cpython-311.pyc ADDED
Binary file (1.95 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/sessions.cpython-311.pyc ADDED
Binary file (5.03 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/trustedhost.cpython-311.pyc ADDED
Binary file (3.54 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/__pycache__/wsgi.cpython-311.pyc ADDED
Binary file (9.69 kB). View file
 
.venv/lib/python3.11/site-packages/starlette/middleware/authentication.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from starlette.authentication import (
6
+ AuthCredentials,
7
+ AuthenticationBackend,
8
+ AuthenticationError,
9
+ UnauthenticatedUser,
10
+ )
11
+ from starlette.requests import HTTPConnection
12
+ from starlette.responses import PlainTextResponse, Response
13
+ from starlette.types import ASGIApp, Receive, Scope, Send
14
+
15
+
16
+ class AuthenticationMiddleware:
17
+ def __init__(
18
+ self,
19
+ app: ASGIApp,
20
+ backend: AuthenticationBackend,
21
+ on_error: typing.Callable[[HTTPConnection, AuthenticationError], Response] | None = None,
22
+ ) -> None:
23
+ self.app = app
24
+ self.backend = backend
25
+ self.on_error: typing.Callable[[HTTPConnection, AuthenticationError], Response] = (
26
+ on_error if on_error is not None else self.default_on_error
27
+ )
28
+
29
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
30
+ if scope["type"] not in ["http", "websocket"]:
31
+ await self.app(scope, receive, send)
32
+ return
33
+
34
+ conn = HTTPConnection(scope)
35
+ try:
36
+ auth_result = await self.backend.authenticate(conn)
37
+ except AuthenticationError as exc:
38
+ response = self.on_error(conn, exc)
39
+ if scope["type"] == "websocket":
40
+ await send({"type": "websocket.close", "code": 1000})
41
+ else:
42
+ await response(scope, receive, send)
43
+ return
44
+
45
+ if auth_result is None:
46
+ auth_result = AuthCredentials(), UnauthenticatedUser()
47
+ scope["auth"], scope["user"] = auth_result
48
+ await self.app(scope, receive, send)
49
+
50
+ @staticmethod
51
+ def default_on_error(conn: HTTPConnection, exc: Exception) -> Response:
52
+ return PlainTextResponse(str(exc), status_code=400)
.venv/lib/python3.11/site-packages/starlette/middleware/base.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ import anyio
6
+
7
+ from starlette._utils import collapse_excgroups
8
+ from starlette.requests import ClientDisconnect, Request
9
+ from starlette.responses import AsyncContentStream, Response
10
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
11
+
12
+ RequestResponseEndpoint = typing.Callable[[Request], typing.Awaitable[Response]]
13
+ DispatchFunction = typing.Callable[[Request, RequestResponseEndpoint], typing.Awaitable[Response]]
14
+ T = typing.TypeVar("T")
15
+
16
+
17
+ class _CachedRequest(Request):
18
+ """
19
+ If the user calls Request.body() from their dispatch function
20
+ we cache the entire request body in memory and pass that to downstream middlewares,
21
+ but if they call Request.stream() then all we do is send an
22
+ empty body so that downstream things don't hang forever.
23
+ """
24
+
25
+ def __init__(self, scope: Scope, receive: Receive):
26
+ super().__init__(scope, receive)
27
+ self._wrapped_rcv_disconnected = False
28
+ self._wrapped_rcv_consumed = False
29
+ self._wrapped_rc_stream = self.stream()
30
+
31
+ async def wrapped_receive(self) -> Message:
32
+ # wrapped_rcv state 1: disconnected
33
+ if self._wrapped_rcv_disconnected:
34
+ # we've already sent a disconnect to the downstream app
35
+ # we don't need to wait to get another one
36
+ # (although most ASGI servers will just keep sending it)
37
+ return {"type": "http.disconnect"}
38
+ # wrapped_rcv state 1: consumed but not yet disconnected
39
+ if self._wrapped_rcv_consumed:
40
+ # since the downstream app has consumed us all that is left
41
+ # is to send it a disconnect
42
+ if self._is_disconnected:
43
+ # the middleware has already seen the disconnect
44
+ # since we know the client is disconnected no need to wait
45
+ # for the message
46
+ self._wrapped_rcv_disconnected = True
47
+ return {"type": "http.disconnect"}
48
+ # we don't know yet if the client is disconnected or not
49
+ # so we'll wait until we get that message
50
+ msg = await self.receive()
51
+ if msg["type"] != "http.disconnect": # pragma: no cover
52
+ # at this point a disconnect is all that we should be receiving
53
+ # if we get something else, things went wrong somewhere
54
+ raise RuntimeError(f"Unexpected message received: {msg['type']}")
55
+ self._wrapped_rcv_disconnected = True
56
+ return msg
57
+
58
+ # wrapped_rcv state 3: not yet consumed
59
+ if getattr(self, "_body", None) is not None:
60
+ # body() was called, we return it even if the client disconnected
61
+ self._wrapped_rcv_consumed = True
62
+ return {
63
+ "type": "http.request",
64
+ "body": self._body,
65
+ "more_body": False,
66
+ }
67
+ elif self._stream_consumed:
68
+ # stream() was called to completion
69
+ # return an empty body so that downstream apps don't hang
70
+ # waiting for a disconnect
71
+ self._wrapped_rcv_consumed = True
72
+ return {
73
+ "type": "http.request",
74
+ "body": b"",
75
+ "more_body": False,
76
+ }
77
+ else:
78
+ # body() was never called and stream() wasn't consumed
79
+ try:
80
+ stream = self.stream()
81
+ chunk = await stream.__anext__()
82
+ self._wrapped_rcv_consumed = self._stream_consumed
83
+ return {
84
+ "type": "http.request",
85
+ "body": chunk,
86
+ "more_body": not self._stream_consumed,
87
+ }
88
+ except ClientDisconnect:
89
+ self._wrapped_rcv_disconnected = True
90
+ return {"type": "http.disconnect"}
91
+
92
+
93
+ class BaseHTTPMiddleware:
94
+ def __init__(self, app: ASGIApp, dispatch: DispatchFunction | None = None) -> None:
95
+ self.app = app
96
+ self.dispatch_func = self.dispatch if dispatch is None else dispatch
97
+
98
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
99
+ if scope["type"] != "http":
100
+ await self.app(scope, receive, send)
101
+ return
102
+
103
+ request = _CachedRequest(scope, receive)
104
+ wrapped_receive = request.wrapped_receive
105
+ response_sent = anyio.Event()
106
+
107
+ async def call_next(request: Request) -> Response:
108
+ app_exc: Exception | None = None
109
+
110
+ async def receive_or_disconnect() -> Message:
111
+ if response_sent.is_set():
112
+ return {"type": "http.disconnect"}
113
+
114
+ async with anyio.create_task_group() as task_group:
115
+
116
+ async def wrap(func: typing.Callable[[], typing.Awaitable[T]]) -> T:
117
+ result = await func()
118
+ task_group.cancel_scope.cancel()
119
+ return result
120
+
121
+ task_group.start_soon(wrap, response_sent.wait)
122
+ message = await wrap(wrapped_receive)
123
+
124
+ if response_sent.is_set():
125
+ return {"type": "http.disconnect"}
126
+
127
+ return message
128
+
129
+ async def send_no_error(message: Message) -> None:
130
+ try:
131
+ await send_stream.send(message)
132
+ except anyio.BrokenResourceError:
133
+ # recv_stream has been closed, i.e. response_sent has been set.
134
+ return
135
+
136
+ async def coro() -> None:
137
+ nonlocal app_exc
138
+
139
+ with send_stream:
140
+ try:
141
+ await self.app(scope, receive_or_disconnect, send_no_error)
142
+ except Exception as exc:
143
+ app_exc = exc
144
+
145
+ task_group.start_soon(coro)
146
+
147
+ try:
148
+ message = await recv_stream.receive()
149
+ info = message.get("info", None)
150
+ if message["type"] == "http.response.debug" and info is not None:
151
+ message = await recv_stream.receive()
152
+ except anyio.EndOfStream:
153
+ if app_exc is not None:
154
+ raise app_exc
155
+ raise RuntimeError("No response returned.")
156
+
157
+ assert message["type"] == "http.response.start"
158
+
159
+ async def body_stream() -> typing.AsyncGenerator[bytes, None]:
160
+ async for message in recv_stream:
161
+ assert message["type"] == "http.response.body"
162
+ body = message.get("body", b"")
163
+ if body:
164
+ yield body
165
+ if not message.get("more_body", False):
166
+ break
167
+
168
+ if app_exc is not None:
169
+ raise app_exc
170
+
171
+ response = _StreamingResponse(status_code=message["status"], content=body_stream(), info=info)
172
+ response.raw_headers = message["headers"]
173
+ return response
174
+
175
+ streams: anyio.create_memory_object_stream[Message] = anyio.create_memory_object_stream()
176
+ send_stream, recv_stream = streams
177
+ with recv_stream, send_stream, collapse_excgroups():
178
+ async with anyio.create_task_group() as task_group:
179
+ response = await self.dispatch_func(request, call_next)
180
+ await response(scope, wrapped_receive, send)
181
+ response_sent.set()
182
+ recv_stream.close()
183
+
184
+ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
185
+ raise NotImplementedError() # pragma: no cover
186
+
187
+
188
+ class _StreamingResponse(Response):
189
+ def __init__(
190
+ self,
191
+ content: AsyncContentStream,
192
+ status_code: int = 200,
193
+ headers: typing.Mapping[str, str] | None = None,
194
+ media_type: str | None = None,
195
+ info: typing.Mapping[str, typing.Any] | None = None,
196
+ ) -> None:
197
+ self.info = info
198
+ self.body_iterator = content
199
+ self.status_code = status_code
200
+ self.media_type = media_type
201
+ self.init_headers(headers)
202
+ self.background = None
203
+
204
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
205
+ if self.info is not None:
206
+ await send({"type": "http.response.debug", "info": self.info})
207
+ await send(
208
+ {
209
+ "type": "http.response.start",
210
+ "status": self.status_code,
211
+ "headers": self.raw_headers,
212
+ }
213
+ )
214
+
215
+ async for chunk in self.body_iterator:
216
+ await send({"type": "http.response.body", "body": chunk, "more_body": True})
217
+
218
+ await send({"type": "http.response.body", "body": b"", "more_body": False})
219
+
220
+ if self.background:
221
+ await self.background()
.venv/lib/python3.11/site-packages/starlette/middleware/cors.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import re
5
+ import typing
6
+
7
+ from starlette.datastructures import Headers, MutableHeaders
8
+ from starlette.responses import PlainTextResponse, Response
9
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
10
+
11
+ ALL_METHODS = ("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT")
12
+ SAFELISTED_HEADERS = {"Accept", "Accept-Language", "Content-Language", "Content-Type"}
13
+
14
+
15
+ class CORSMiddleware:
16
+ def __init__(
17
+ self,
18
+ app: ASGIApp,
19
+ allow_origins: typing.Sequence[str] = (),
20
+ allow_methods: typing.Sequence[str] = ("GET",),
21
+ allow_headers: typing.Sequence[str] = (),
22
+ allow_credentials: bool = False,
23
+ allow_origin_regex: str | None = None,
24
+ expose_headers: typing.Sequence[str] = (),
25
+ max_age: int = 600,
26
+ ) -> None:
27
+ if "*" in allow_methods:
28
+ allow_methods = ALL_METHODS
29
+
30
+ compiled_allow_origin_regex = None
31
+ if allow_origin_regex is not None:
32
+ compiled_allow_origin_regex = re.compile(allow_origin_regex)
33
+
34
+ allow_all_origins = "*" in allow_origins
35
+ allow_all_headers = "*" in allow_headers
36
+ preflight_explicit_allow_origin = not allow_all_origins or allow_credentials
37
+
38
+ simple_headers = {}
39
+ if allow_all_origins:
40
+ simple_headers["Access-Control-Allow-Origin"] = "*"
41
+ if allow_credentials:
42
+ simple_headers["Access-Control-Allow-Credentials"] = "true"
43
+ if expose_headers:
44
+ simple_headers["Access-Control-Expose-Headers"] = ", ".join(expose_headers)
45
+
46
+ preflight_headers = {}
47
+ if preflight_explicit_allow_origin:
48
+ # The origin value will be set in preflight_response() if it is allowed.
49
+ preflight_headers["Vary"] = "Origin"
50
+ else:
51
+ preflight_headers["Access-Control-Allow-Origin"] = "*"
52
+ preflight_headers.update(
53
+ {
54
+ "Access-Control-Allow-Methods": ", ".join(allow_methods),
55
+ "Access-Control-Max-Age": str(max_age),
56
+ }
57
+ )
58
+ allow_headers = sorted(SAFELISTED_HEADERS | set(allow_headers))
59
+ if allow_headers and not allow_all_headers:
60
+ preflight_headers["Access-Control-Allow-Headers"] = ", ".join(allow_headers)
61
+ if allow_credentials:
62
+ preflight_headers["Access-Control-Allow-Credentials"] = "true"
63
+
64
+ self.app = app
65
+ self.allow_origins = allow_origins
66
+ self.allow_methods = allow_methods
67
+ self.allow_headers = [h.lower() for h in allow_headers]
68
+ self.allow_all_origins = allow_all_origins
69
+ self.allow_all_headers = allow_all_headers
70
+ self.preflight_explicit_allow_origin = preflight_explicit_allow_origin
71
+ self.allow_origin_regex = compiled_allow_origin_regex
72
+ self.simple_headers = simple_headers
73
+ self.preflight_headers = preflight_headers
74
+
75
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
76
+ if scope["type"] != "http": # pragma: no cover
77
+ await self.app(scope, receive, send)
78
+ return
79
+
80
+ method = scope["method"]
81
+ headers = Headers(scope=scope)
82
+ origin = headers.get("origin")
83
+
84
+ if origin is None:
85
+ await self.app(scope, receive, send)
86
+ return
87
+
88
+ if method == "OPTIONS" and "access-control-request-method" in headers:
89
+ response = self.preflight_response(request_headers=headers)
90
+ await response(scope, receive, send)
91
+ return
92
+
93
+ await self.simple_response(scope, receive, send, request_headers=headers)
94
+
95
+ def is_allowed_origin(self, origin: str) -> bool:
96
+ if self.allow_all_origins:
97
+ return True
98
+
99
+ if self.allow_origin_regex is not None and self.allow_origin_regex.fullmatch(origin):
100
+ return True
101
+
102
+ return origin in self.allow_origins
103
+
104
+ def preflight_response(self, request_headers: Headers) -> Response:
105
+ requested_origin = request_headers["origin"]
106
+ requested_method = request_headers["access-control-request-method"]
107
+ requested_headers = request_headers.get("access-control-request-headers")
108
+
109
+ headers = dict(self.preflight_headers)
110
+ failures = []
111
+
112
+ if self.is_allowed_origin(origin=requested_origin):
113
+ if self.preflight_explicit_allow_origin:
114
+ # The "else" case is already accounted for in self.preflight_headers
115
+ # and the value would be "*".
116
+ headers["Access-Control-Allow-Origin"] = requested_origin
117
+ else:
118
+ failures.append("origin")
119
+
120
+ if requested_method not in self.allow_methods:
121
+ failures.append("method")
122
+
123
+ # If we allow all headers, then we have to mirror back any requested
124
+ # headers in the response.
125
+ if self.allow_all_headers and requested_headers is not None:
126
+ headers["Access-Control-Allow-Headers"] = requested_headers
127
+ elif requested_headers is not None:
128
+ for header in [h.lower() for h in requested_headers.split(",")]:
129
+ if header.strip() not in self.allow_headers:
130
+ failures.append("headers")
131
+ break
132
+
133
+ # We don't strictly need to use 400 responses here, since its up to
134
+ # the browser to enforce the CORS policy, but its more informative
135
+ # if we do.
136
+ if failures:
137
+ failure_text = "Disallowed CORS " + ", ".join(failures)
138
+ return PlainTextResponse(failure_text, status_code=400, headers=headers)
139
+
140
+ return PlainTextResponse("OK", status_code=200, headers=headers)
141
+
142
+ async def simple_response(self, scope: Scope, receive: Receive, send: Send, request_headers: Headers) -> None:
143
+ send = functools.partial(self.send, send=send, request_headers=request_headers)
144
+ await self.app(scope, receive, send)
145
+
146
+ async def send(self, message: Message, send: Send, request_headers: Headers) -> None:
147
+ if message["type"] != "http.response.start":
148
+ await send(message)
149
+ return
150
+
151
+ message.setdefault("headers", [])
152
+ headers = MutableHeaders(scope=message)
153
+ headers.update(self.simple_headers)
154
+ origin = request_headers["Origin"]
155
+ has_cookie = "cookie" in request_headers
156
+
157
+ # If request includes any cookie headers, then we must respond
158
+ # with the specific origin instead of '*'.
159
+ if self.allow_all_origins and has_cookie:
160
+ self.allow_explicit_origin(headers, origin)
161
+
162
+ # If we only allow specific origins, then we have to mirror back
163
+ # the Origin header in the response.
164
+ elif not self.allow_all_origins and self.is_allowed_origin(origin=origin):
165
+ self.allow_explicit_origin(headers, origin)
166
+
167
+ await send(message)
168
+
169
+ @staticmethod
170
+ def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None:
171
+ headers["Access-Control-Allow-Origin"] = origin
172
+ headers.add_vary_header("Origin")
.venv/lib/python3.11/site-packages/starlette/middleware/errors.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import html
4
+ import inspect
5
+ import sys
6
+ import traceback
7
+ import typing
8
+
9
+ from starlette._utils import is_async_callable
10
+ from starlette.concurrency import run_in_threadpool
11
+ from starlette.requests import Request
12
+ from starlette.responses import HTMLResponse, PlainTextResponse, Response
13
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
14
+
15
+ STYLES = """
16
+ p {
17
+ color: #211c1c;
18
+ }
19
+ .traceback-container {
20
+ border: 1px solid #038BB8;
21
+ }
22
+ .traceback-title {
23
+ background-color: #038BB8;
24
+ color: lemonchiffon;
25
+ padding: 12px;
26
+ font-size: 20px;
27
+ margin-top: 0px;
28
+ }
29
+ .frame-line {
30
+ padding-left: 10px;
31
+ font-family: monospace;
32
+ }
33
+ .frame-filename {
34
+ font-family: monospace;
35
+ }
36
+ .center-line {
37
+ background-color: #038BB8;
38
+ color: #f9f6e1;
39
+ padding: 5px 0px 5px 5px;
40
+ }
41
+ .lineno {
42
+ margin-right: 5px;
43
+ }
44
+ .frame-title {
45
+ font-weight: unset;
46
+ padding: 10px 10px 10px 10px;
47
+ background-color: #E4F4FD;
48
+ margin-right: 10px;
49
+ color: #191f21;
50
+ font-size: 17px;
51
+ border: 1px solid #c7dce8;
52
+ }
53
+ .collapse-btn {
54
+ float: right;
55
+ padding: 0px 5px 1px 5px;
56
+ border: solid 1px #96aebb;
57
+ cursor: pointer;
58
+ }
59
+ .collapsed {
60
+ display: none;
61
+ }
62
+ .source-code {
63
+ font-family: courier;
64
+ font-size: small;
65
+ padding-bottom: 10px;
66
+ }
67
+ """
68
+
69
+ JS = """
70
+ <script type="text/javascript">
71
+ function collapse(element){
72
+ const frameId = element.getAttribute("data-frame-id");
73
+ const frame = document.getElementById(frameId);
74
+
75
+ if (frame.classList.contains("collapsed")){
76
+ element.innerHTML = "&#8210;";
77
+ frame.classList.remove("collapsed");
78
+ } else {
79
+ element.innerHTML = "+";
80
+ frame.classList.add("collapsed");
81
+ }
82
+ }
83
+ </script>
84
+ """
85
+
86
+ TEMPLATE = """
87
+ <html>
88
+ <head>
89
+ <style type='text/css'>
90
+ {styles}
91
+ </style>
92
+ <title>Starlette Debugger</title>
93
+ </head>
94
+ <body>
95
+ <h1>500 Server Error</h1>
96
+ <h2>{error}</h2>
97
+ <div class="traceback-container">
98
+ <p class="traceback-title">Traceback</p>
99
+ <div>{exc_html}</div>
100
+ </div>
101
+ {js}
102
+ </body>
103
+ </html>
104
+ """
105
+
106
+ FRAME_TEMPLATE = """
107
+ <div>
108
+ <p class="frame-title">File <span class="frame-filename">{frame_filename}</span>,
109
+ line <i>{frame_lineno}</i>,
110
+ in <b>{frame_name}</b>
111
+ <span class="collapse-btn" data-frame-id="{frame_filename}-{frame_lineno}" onclick="collapse(this)">{collapse_button}</span>
112
+ </p>
113
+ <div id="{frame_filename}-{frame_lineno}" class="source-code {collapsed}">{code_context}</div>
114
+ </div>
115
+ """ # noqa: E501
116
+
117
+ LINE = """
118
+ <p><span class="frame-line">
119
+ <span class="lineno">{lineno}.</span> {line}</span></p>
120
+ """
121
+
122
+ CENTER_LINE = """
123
+ <p class="center-line"><span class="frame-line center-line">
124
+ <span class="lineno">{lineno}.</span> {line}</span></p>
125
+ """
126
+
127
+
128
+ class ServerErrorMiddleware:
129
+ """
130
+ Handles returning 500 responses when a server error occurs.
131
+
132
+ If 'debug' is set, then traceback responses will be returned,
133
+ otherwise the designated 'handler' will be called.
134
+
135
+ This middleware class should generally be used to wrap *everything*
136
+ else up, so that unhandled exceptions anywhere in the stack
137
+ always result in an appropriate 500 response.
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ app: ASGIApp,
143
+ handler: typing.Callable[[Request, Exception], typing.Any] | None = None,
144
+ debug: bool = False,
145
+ ) -> None:
146
+ self.app = app
147
+ self.handler = handler
148
+ self.debug = debug
149
+
150
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
151
+ if scope["type"] != "http":
152
+ await self.app(scope, receive, send)
153
+ return
154
+
155
+ response_started = False
156
+
157
+ async def _send(message: Message) -> None:
158
+ nonlocal response_started, send
159
+
160
+ if message["type"] == "http.response.start":
161
+ response_started = True
162
+ await send(message)
163
+
164
+ try:
165
+ await self.app(scope, receive, _send)
166
+ except Exception as exc:
167
+ request = Request(scope)
168
+ if self.debug:
169
+ # In debug mode, return traceback responses.
170
+ response = self.debug_response(request, exc)
171
+ elif self.handler is None:
172
+ # Use our default 500 error handler.
173
+ response = self.error_response(request, exc)
174
+ else:
175
+ # Use an installed 500 error handler.
176
+ if is_async_callable(self.handler):
177
+ response = await self.handler(request, exc)
178
+ else:
179
+ response = await run_in_threadpool(self.handler, request, exc)
180
+
181
+ if not response_started:
182
+ await response(scope, receive, send)
183
+
184
+ # We always continue to raise the exception.
185
+ # This allows servers to log the error, or allows test clients
186
+ # to optionally raise the error within the test case.
187
+ raise exc
188
+
189
+ def format_line(self, index: int, line: str, frame_lineno: int, frame_index: int) -> str:
190
+ values = {
191
+ # HTML escape - line could contain < or >
192
+ "line": html.escape(line).replace(" ", "&nbsp"),
193
+ "lineno": (frame_lineno - frame_index) + index,
194
+ }
195
+
196
+ if index != frame_index:
197
+ return LINE.format(**values)
198
+ return CENTER_LINE.format(**values)
199
+
200
+ def generate_frame_html(self, frame: inspect.FrameInfo, is_collapsed: bool) -> str:
201
+ code_context = "".join(
202
+ self.format_line(
203
+ index,
204
+ line,
205
+ frame.lineno,
206
+ frame.index, # type: ignore[arg-type]
207
+ )
208
+ for index, line in enumerate(frame.code_context or [])
209
+ )
210
+
211
+ values = {
212
+ # HTML escape - filename could contain < or >, especially if it's a virtual
213
+ # file e.g. <stdin> in the REPL
214
+ "frame_filename": html.escape(frame.filename),
215
+ "frame_lineno": frame.lineno,
216
+ # HTML escape - if you try very hard it's possible to name a function with <
217
+ # or >
218
+ "frame_name": html.escape(frame.function),
219
+ "code_context": code_context,
220
+ "collapsed": "collapsed" if is_collapsed else "",
221
+ "collapse_button": "+" if is_collapsed else "&#8210;",
222
+ }
223
+ return FRAME_TEMPLATE.format(**values)
224
+
225
+ def generate_html(self, exc: Exception, limit: int = 7) -> str:
226
+ traceback_obj = traceback.TracebackException.from_exception(exc, capture_locals=True)
227
+
228
+ exc_html = ""
229
+ is_collapsed = False
230
+ exc_traceback = exc.__traceback__
231
+ if exc_traceback is not None:
232
+ frames = inspect.getinnerframes(exc_traceback, limit)
233
+ for frame in reversed(frames):
234
+ exc_html += self.generate_frame_html(frame, is_collapsed)
235
+ is_collapsed = True
236
+
237
+ if sys.version_info >= (3, 13): # pragma: no cover
238
+ exc_type_str = traceback_obj.exc_type_str
239
+ else: # pragma: no cover
240
+ exc_type_str = traceback_obj.exc_type.__name__
241
+
242
+ # escape error class and text
243
+ error = f"{html.escape(exc_type_str)}: {html.escape(str(traceback_obj))}"
244
+
245
+ return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html)
246
+
247
+ def generate_plain_text(self, exc: Exception) -> str:
248
+ return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
249
+
250
+ def debug_response(self, request: Request, exc: Exception) -> Response:
251
+ accept = request.headers.get("accept", "")
252
+
253
+ if "text/html" in accept:
254
+ content = self.generate_html(exc)
255
+ return HTMLResponse(content, status_code=500)
256
+ content = self.generate_plain_text(exc)
257
+ return PlainTextResponse(content, status_code=500)
258
+
259
+ def error_response(self, request: Request, exc: Exception) -> Response:
260
+ return PlainTextResponse("Internal Server Error", status_code=500)
.venv/lib/python3.11/site-packages/starlette/middleware/exceptions.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from starlette._exception_handler import (
6
+ ExceptionHandlers,
7
+ StatusHandlers,
8
+ wrap_app_handling_exceptions,
9
+ )
10
+ from starlette.exceptions import HTTPException, WebSocketException
11
+ from starlette.requests import Request
12
+ from starlette.responses import PlainTextResponse, Response
13
+ from starlette.types import ASGIApp, Receive, Scope, Send
14
+ from starlette.websockets import WebSocket
15
+
16
+
17
+ class ExceptionMiddleware:
18
+ def __init__(
19
+ self,
20
+ app: ASGIApp,
21
+ handlers: typing.Mapping[typing.Any, typing.Callable[[Request, Exception], Response]] | None = None,
22
+ debug: bool = False,
23
+ ) -> None:
24
+ self.app = app
25
+ self.debug = debug # TODO: We ought to handle 404 cases if debug is set.
26
+ self._status_handlers: StatusHandlers = {}
27
+ self._exception_handlers: ExceptionHandlers = {
28
+ HTTPException: self.http_exception,
29
+ WebSocketException: self.websocket_exception,
30
+ }
31
+ if handlers is not None: # pragma: no branch
32
+ for key, value in handlers.items():
33
+ self.add_exception_handler(key, value)
34
+
35
+ def add_exception_handler(
36
+ self,
37
+ exc_class_or_status_code: int | type[Exception],
38
+ handler: typing.Callable[[Request, Exception], Response],
39
+ ) -> None:
40
+ if isinstance(exc_class_or_status_code, int):
41
+ self._status_handlers[exc_class_or_status_code] = handler
42
+ else:
43
+ assert issubclass(exc_class_or_status_code, Exception)
44
+ self._exception_handlers[exc_class_or_status_code] = handler
45
+
46
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
47
+ if scope["type"] not in ("http", "websocket"):
48
+ await self.app(scope, receive, send)
49
+ return
50
+
51
+ scope["starlette.exception_handlers"] = (
52
+ self._exception_handlers,
53
+ self._status_handlers,
54
+ )
55
+
56
+ conn: Request | WebSocket
57
+ if scope["type"] == "http":
58
+ conn = Request(scope, receive, send)
59
+ else:
60
+ conn = WebSocket(scope, receive, send)
61
+
62
+ await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
63
+
64
+ def http_exception(self, request: Request, exc: Exception) -> Response:
65
+ assert isinstance(exc, HTTPException)
66
+ if exc.status_code in {204, 304}:
67
+ return Response(status_code=exc.status_code, headers=exc.headers)
68
+ return PlainTextResponse(exc.detail, status_code=exc.status_code, headers=exc.headers)
69
+
70
+ async def websocket_exception(self, websocket: WebSocket, exc: Exception) -> None:
71
+ assert isinstance(exc, WebSocketException)
72
+ await websocket.close(code=exc.code, reason=exc.reason) # pragma: no cover
.venv/lib/python3.11/site-packages/starlette/middleware/gzip.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gzip
2
+ import io
3
+ import typing
4
+
5
+ from starlette.datastructures import Headers, MutableHeaders
6
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
7
+
8
+
9
+ class GZipMiddleware:
10
+ def __init__(self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9) -> None:
11
+ self.app = app
12
+ self.minimum_size = minimum_size
13
+ self.compresslevel = compresslevel
14
+
15
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
16
+ if scope["type"] == "http": # pragma: no branch
17
+ headers = Headers(scope=scope)
18
+ if "gzip" in headers.get("Accept-Encoding", ""):
19
+ responder = GZipResponder(self.app, self.minimum_size, compresslevel=self.compresslevel)
20
+ await responder(scope, receive, send)
21
+ return
22
+ await self.app(scope, receive, send)
23
+
24
+
25
+ class GZipResponder:
26
+ def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int = 9) -> None:
27
+ self.app = app
28
+ self.minimum_size = minimum_size
29
+ self.send: Send = unattached_send
30
+ self.initial_message: Message = {}
31
+ self.started = False
32
+ self.content_encoding_set = False
33
+ self.gzip_buffer = io.BytesIO()
34
+ self.gzip_file = gzip.GzipFile(mode="wb", fileobj=self.gzip_buffer, compresslevel=compresslevel)
35
+
36
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
37
+ self.send = send
38
+ with self.gzip_buffer, self.gzip_file:
39
+ await self.app(scope, receive, self.send_with_gzip)
40
+
41
+ async def send_with_gzip(self, message: Message) -> None:
42
+ message_type = message["type"]
43
+ if message_type == "http.response.start":
44
+ # Don't send the initial message until we've determined how to
45
+ # modify the outgoing headers correctly.
46
+ self.initial_message = message
47
+ headers = Headers(raw=self.initial_message["headers"])
48
+ self.content_encoding_set = "content-encoding" in headers
49
+ elif message_type == "http.response.body" and self.content_encoding_set:
50
+ if not self.started:
51
+ self.started = True
52
+ await self.send(self.initial_message)
53
+ await self.send(message)
54
+ elif message_type == "http.response.body" and not self.started:
55
+ self.started = True
56
+ body = message.get("body", b"")
57
+ more_body = message.get("more_body", False)
58
+ if len(body) < self.minimum_size and not more_body:
59
+ # Don't apply GZip to small outgoing responses.
60
+ await self.send(self.initial_message)
61
+ await self.send(message)
62
+ elif not more_body:
63
+ # Standard GZip response.
64
+ self.gzip_file.write(body)
65
+ self.gzip_file.close()
66
+ body = self.gzip_buffer.getvalue()
67
+
68
+ headers = MutableHeaders(raw=self.initial_message["headers"])
69
+ headers["Content-Encoding"] = "gzip"
70
+ headers["Content-Length"] = str(len(body))
71
+ headers.add_vary_header("Accept-Encoding")
72
+ message["body"] = body
73
+
74
+ await self.send(self.initial_message)
75
+ await self.send(message)
76
+ else:
77
+ # Initial body in streaming GZip response.
78
+ headers = MutableHeaders(raw=self.initial_message["headers"])
79
+ headers["Content-Encoding"] = "gzip"
80
+ headers.add_vary_header("Accept-Encoding")
81
+ del headers["Content-Length"]
82
+
83
+ self.gzip_file.write(body)
84
+ message["body"] = self.gzip_buffer.getvalue()
85
+ self.gzip_buffer.seek(0)
86
+ self.gzip_buffer.truncate()
87
+
88
+ await self.send(self.initial_message)
89
+ await self.send(message)
90
+
91
+ elif message_type == "http.response.body": # pragma: no branch
92
+ # Remaining body in streaming GZip response.
93
+ body = message.get("body", b"")
94
+ more_body = message.get("more_body", False)
95
+
96
+ self.gzip_file.write(body)
97
+ if not more_body:
98
+ self.gzip_file.close()
99
+
100
+ message["body"] = self.gzip_buffer.getvalue()
101
+ self.gzip_buffer.seek(0)
102
+ self.gzip_buffer.truncate()
103
+
104
+ await self.send(message)
105
+
106
+
107
+ async def unattached_send(message: Message) -> typing.NoReturn:
108
+ raise RuntimeError("send awaitable not set") # pragma: no cover
.venv/lib/python3.11/site-packages/starlette/middleware/httpsredirect.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from starlette.datastructures import URL
2
+ from starlette.responses import RedirectResponse
3
+ from starlette.types import ASGIApp, Receive, Scope, Send
4
+
5
+
6
+ class HTTPSRedirectMiddleware:
7
+ def __init__(self, app: ASGIApp) -> None:
8
+ self.app = app
9
+
10
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
11
+ if scope["type"] in ("http", "websocket") and scope["scheme"] in ("http", "ws"):
12
+ url = URL(scope=scope)
13
+ redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme]
14
+ netloc = url.hostname if url.port in (80, 443) else url.netloc
15
+ url = url.replace(scheme=redirect_scheme, netloc=netloc)
16
+ response = RedirectResponse(url, status_code=307)
17
+ await response(scope, receive, send)
18
+ else:
19
+ await self.app(scope, receive, send)