NoWon1 commited on
Commit
54bb6a3
·
verified ·
1 Parent(s): 87d40bc

Upload folder using huggingface_hub (part 16)

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 +23 -0
  2. .venv/Lib/site-packages/urllib3-2.7.0.dist-info/INSTALLER +1 -0
  3. .venv/Lib/site-packages/urllib3-2.7.0.dist-info/METADATA +163 -0
  4. .venv/Lib/site-packages/urllib3-2.7.0.dist-info/RECORD +79 -0
  5. .venv/Lib/site-packages/urllib3-2.7.0.dist-info/WHEEL +4 -0
  6. .venv/Lib/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt +21 -0
  7. .venv/Lib/site-packages/urllib3/util/wait.py +124 -0
  8. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/INSTALLER +1 -0
  9. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/METADATA +189 -0
  10. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/RECORD +90 -0
  11. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/WHEEL +4 -0
  12. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/entry_points.txt +2 -0
  13. .venv/Lib/site-packages/uvicorn-0.51.0.dist-info/licenses/LICENSE.md +27 -0
  14. .venv/Lib/site-packages/uvicorn/__init__.py +5 -0
  15. .venv/Lib/site-packages/uvicorn/__main__.py +4 -0
  16. .venv/Lib/site-packages/uvicorn/_ansi.py +29 -0
  17. .venv/Lib/site-packages/uvicorn/_compat.py +91 -0
  18. .venv/Lib/site-packages/uvicorn/_subprocess.py +84 -0
  19. .venv/Lib/site-packages/uvicorn/_types.py +274 -0
  20. .venv/Lib/site-packages/uvicorn/config.py +600 -0
  21. .venv/Lib/site-packages/uvicorn/importer.py +34 -0
  22. .venv/Lib/site-packages/uvicorn/lifespan/__init__.py +0 -0
  23. .venv/Lib/site-packages/uvicorn/lifespan/off.py +17 -0
  24. .venv/Lib/site-packages/uvicorn/lifespan/on.py +137 -0
  25. .venv/Lib/site-packages/uvicorn/logging.py +117 -0
  26. .venv/Lib/site-packages/uvicorn/loops/__init__.py +0 -0
  27. .venv/Lib/site-packages/uvicorn/loops/asyncio.py +11 -0
  28. .venv/Lib/site-packages/uvicorn/loops/auto.py +17 -0
  29. .venv/Lib/site-packages/uvicorn/loops/uvloop.py +10 -0
  30. .venv/Lib/site-packages/uvicorn/main.py +645 -0
  31. .venv/Lib/site-packages/uvicorn/middleware/__init__.py +0 -0
  32. .venv/Lib/site-packages/uvicorn/middleware/asgi2.py +15 -0
  33. .venv/Lib/site-packages/uvicorn/middleware/message_logger.py +87 -0
  34. .venv/Lib/site-packages/uvicorn/middleware/proxy_headers.py +187 -0
  35. .venv/Lib/site-packages/uvicorn/middleware/wsgi.py +199 -0
  36. .venv/Lib/site-packages/uvicorn/protocols/__init__.py +0 -0
  37. .venv/Lib/site-packages/uvicorn/protocols/http/__init__.py +0 -0
  38. .venv/Lib/site-packages/uvicorn/protocols/http/auto.py +15 -0
  39. .venv/Lib/site-packages/uvicorn/protocols/http/flow_control.py +54 -0
  40. .venv/Lib/site-packages/uvicorn/protocols/http/h11_impl.py +545 -0
  41. .venv/Lib/site-packages/uvicorn/protocols/http/httptools_impl.py +577 -0
  42. .venv/Lib/site-packages/uvicorn/protocols/utils.py +62 -0
  43. .venv/Lib/site-packages/uvicorn/protocols/websockets/__init__.py +0 -0
  44. .venv/Lib/site-packages/uvicorn/protocols/websockets/auto.py +21 -0
  45. .venv/Lib/site-packages/uvicorn/protocols/websockets/websockets_impl.py +380 -0
  46. .venv/Lib/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py +484 -0
  47. .venv/Lib/site-packages/uvicorn/protocols/websockets/wsproto_impl.py +469 -0
  48. .venv/Lib/site-packages/uvicorn/py.typed +1 -0
  49. .venv/Lib/site-packages/uvicorn/server.py +344 -0
  50. .venv/Lib/site-packages/uvicorn/supervisors/__init__.py +16 -0
.gitattributes CHANGED
@@ -72,3 +72,26 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
72
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSansVF-Upright.ttf.BsWL4Kly.woff2 filter=lfs diff=lfs merge=lfs -text
73
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSerifVariable-Italic.ttf.CVdzAtxO.woff2 filter=lfs diff=lfs merge=lfs -text
74
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSerifVariable-Roman.ttf.mdpVL9bi.woff2 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSansVF-Upright.ttf.BsWL4Kly.woff2 filter=lfs diff=lfs merge=lfs -text
73
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSerifVariable-Italic.ttf.CVdzAtxO.woff2 filter=lfs diff=lfs merge=lfs -text
74
  .venv/Lib/site-packages/streamlit/static/static/media/SourceSerifVariable-Roman.ttf.mdpVL9bi.woff2 filter=lfs diff=lfs merge=lfs -text
75
+ .venv/Scripts/f2py.exe filter=lfs diff=lfs merge=lfs -text
76
+ .venv/Scripts/hf.exe filter=lfs diff=lfs merge=lfs -text
77
+ .venv/Scripts/httpx.exe filter=lfs diff=lfs merge=lfs -text
78
+ .venv/Scripts/huggingface-cli.exe filter=lfs diff=lfs merge=lfs -text
79
+ .venv/Scripts/idna.exe filter=lfs diff=lfs merge=lfs -text
80
+ .venv/Scripts/jsonschema.exe filter=lfs diff=lfs merge=lfs -text
81
+ .venv/Scripts/normalizer.exe filter=lfs diff=lfs merge=lfs -text
82
+ .venv/Scripts/numpy-config.exe filter=lfs diff=lfs merge=lfs -text
83
+ .venv/Scripts/pip.exe filter=lfs diff=lfs merge=lfs -text
84
+ .venv/Scripts/pip3.12.exe filter=lfs diff=lfs merge=lfs -text
85
+ .venv/Scripts/pip3.exe filter=lfs diff=lfs merge=lfs -text
86
+ .venv/Scripts/py.test.exe filter=lfs diff=lfs merge=lfs -text
87
+ .venv/Scripts/pygmentize.exe filter=lfs diff=lfs merge=lfs -text
88
+ .venv/Scripts/pytest.exe filter=lfs diff=lfs merge=lfs -text
89
+ .venv/Scripts/python.exe filter=lfs diff=lfs merge=lfs -text
90
+ .venv/Scripts/pythonw.exe filter=lfs diff=lfs merge=lfs -text
91
+ .venv/Scripts/streamlit.exe filter=lfs diff=lfs merge=lfs -text
92
+ .venv/Scripts/tiny-agents.exe filter=lfs diff=lfs merge=lfs -text
93
+ .venv/Scripts/tqdm.exe filter=lfs diff=lfs merge=lfs -text
94
+ .venv/Scripts/uvicorn.exe filter=lfs diff=lfs merge=lfs -text
95
+ .venv/Scripts/watchmedo.exe filter=lfs diff=lfs merge=lfs -text
96
+ .venv/Scripts/websockets.exe filter=lfs diff=lfs merge=lfs -text
97
+ .venv/share/jupyter/nbextensions/pydeck/index.js.map filter=lfs diff=lfs merge=lfs -text
.venv/Lib/site-packages/urllib3-2.7.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.venv/Lib/site-packages/urllib3-2.7.0.dist-info/METADATA ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: urllib3
3
+ Version: 2.7.0
4
+ Summary: HTTP library with thread-safe connection pooling, file post, and more.
5
+ Project-URL: Changelog, https://github.com/urllib3/urllib3/blob/main/CHANGES.rst
6
+ Project-URL: Documentation, https://urllib3.readthedocs.io
7
+ Project-URL: Code, https://github.com/urllib3/urllib3
8
+ Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues
9
+ Author-email: Andrey Petrov <andrey.petrov@shazow.net>
10
+ Maintainer-email: Seth Michael Larson <sethmichaellarson@gmail.com>, Quentin Pradet <quentin@pradet.me>, Illia Volochii <illia.volochii@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE.txt
13
+ Keywords: filepost,http,httplib,https,pooling,ssl,threadsafe,urllib
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
26
+ Classifier: Programming Language :: Python :: Implementation :: CPython
27
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
28
+ Classifier: Topic :: Internet :: WWW/HTTP
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Requires-Python: >=3.10
31
+ Provides-Extra: brotli
32
+ Requires-Dist: brotli>=1.2.0; (platform_python_implementation == 'CPython') and extra == 'brotli'
33
+ Requires-Dist: brotlicffi>=1.2.0.0; (platform_python_implementation != 'CPython') and extra == 'brotli'
34
+ Provides-Extra: h2
35
+ Requires-Dist: h2<5,>=4; extra == 'h2'
36
+ Provides-Extra: socks
37
+ Requires-Dist: pysocks!=1.5.7,<2.0,>=1.5.6; extra == 'socks'
38
+ Provides-Extra: zstd
39
+ Requires-Dist: backports-zstd>=1.0.0; (python_version < '3.14') and extra == 'zstd'
40
+ Description-Content-Type: text/markdown
41
+
42
+ <h1 align="center">
43
+
44
+ ![urllib3](https://github.com/urllib3/urllib3/raw/main/docs/_static/banner_github.svg)
45
+
46
+ </h1>
47
+
48
+ <p align="center">
49
+ <a href="https://pypi.org/project/urllib3"><img alt="PyPI Version" src="https://img.shields.io/pypi/v/urllib3.svg?maxAge=86400" /></a>
50
+ <a href="https://pypi.org/project/urllib3"><img alt="Python Versions" src="https://img.shields.io/pypi/pyversions/urllib3.svg?maxAge=86400" /></a>
51
+ <a href="https://discord.gg/urllib3"><img alt="Join our Discord" src="https://img.shields.io/discord/756342717725933608?color=%237289da&label=discord" /></a>
52
+ <a href="https://github.com/urllib3/urllib3/actions?query=workflow%3ACI"><img alt="Coverage Status" src="https://img.shields.io/badge/coverage-100%25-success" /></a>
53
+ <a href="https://github.com/urllib3/urllib3/actions/workflows/ci.yml?query=branch%3Amain"><img alt="Build Status on GitHub" src="https://github.com/urllib3/urllib3/actions/workflows/ci.yml/badge.svg?branch:main&workflow:CI" /></a>
54
+ <a href="https://urllib3.readthedocs.io"><img alt="Documentation Status" src="https://readthedocs.org/projects/urllib3/badge/?version=latest" /></a><br>
55
+ <a href="https://deps.dev/pypi/urllib3"><img alt="OpenSSF Scorecard" src="https://api.securityscorecards.dev/projects/github.com/urllib3/urllib3/badge" /></a>
56
+ <a href="https://slsa.dev"><img alt="SLSA 3" src="https://slsa.dev/images/gh-badge-level3.svg" /></a>
57
+ <a href="https://bestpractices.coreinfrastructure.org/projects/6227"><img alt="CII Best Practices" src="https://bestpractices.coreinfrastructure.org/projects/6227/badge" /></a>
58
+ </p>
59
+
60
+ urllib3 is a powerful, *user-friendly* HTTP client for Python.
61
+ urllib3 brings many critical features that are missing from the Python
62
+ standard libraries:
63
+
64
+ - Thread safety.
65
+ - Connection pooling.
66
+ - Client-side SSL/TLS verification.
67
+ - File uploads with multipart encoding.
68
+ - Helpers for retrying requests and dealing with HTTP redirects.
69
+ - Support for gzip, deflate, brotli, and zstd encoding.
70
+ - Proxy support for HTTP and SOCKS.
71
+ - 100% test coverage.
72
+
73
+ ... and many more features, but most importantly: Our maintainers have a 15+
74
+ year track record of maintaining urllib3 with the highest code standards and
75
+ attention to security and safety.
76
+
77
+ [Much of the Python ecosystem already uses urllib3](https://urllib3.readthedocs.io/en/stable/#who-uses)
78
+ and you should too.
79
+
80
+
81
+ ## Installing
82
+
83
+ urllib3 can be installed with [pip](https://pip.pypa.io):
84
+
85
+ ```bash
86
+ $ python -m pip install urllib3
87
+ ```
88
+
89
+ Alternatively, you can grab the latest source code from [GitHub](https://github.com/urllib3/urllib3):
90
+
91
+ ```bash
92
+ $ git clone https://github.com/urllib3/urllib3.git
93
+ $ cd urllib3
94
+ $ pip install .
95
+ ```
96
+
97
+ ## Getting Started
98
+
99
+ urllib3 is easy to use:
100
+
101
+ ```python3
102
+ >>> import urllib3
103
+ >>> resp = urllib3.request("GET", "http://httpbin.org/robots.txt")
104
+ >>> resp.status
105
+ 200
106
+ >>> resp.data
107
+ b"User-agent: *\nDisallow: /deny\n"
108
+ ```
109
+
110
+ urllib3 has usage and reference documentation at [urllib3.readthedocs.io](https://urllib3.readthedocs.io).
111
+
112
+
113
+ ## Community
114
+
115
+ urllib3 has a [community Discord channel](https://discord.gg/urllib3) for asking questions and
116
+ collaborating with other contributors. Drop by and say hello 👋
117
+
118
+
119
+ ## Contributing
120
+
121
+ urllib3 happily accepts contributions. Please see our
122
+ [contributing documentation](https://urllib3.readthedocs.io/en/latest/contributing.html)
123
+ for some tips on getting started.
124
+
125
+
126
+ ## Security Disclosures
127
+
128
+ To report a security vulnerability, please use the
129
+ [Tidelift security contact](https://tidelift.com/security).
130
+ Tidelift will coordinate the fix and disclosure with maintainers.
131
+
132
+
133
+ ## Maintainers
134
+
135
+ Meet our maintainers since 2008:
136
+
137
+ - Current Lead: [@illia-v](https://github.com/illia-v) (Illia Volochii)
138
+ - [@sethmlarson](https://github.com/sethmlarson) (Seth M. Larson)
139
+ - [@pquentin](https://github.com/pquentin) (Quentin Pradet)
140
+ - [@theacodes](https://github.com/theacodes) (Thea Flowers)
141
+ - [@haikuginger](https://github.com/haikuginger) (Jess Shapiro)
142
+ - [@lukasa](https://github.com/lukasa) (Cory Benfield)
143
+ - [@sigmavirus24](https://github.com/sigmavirus24) (Ian Stapleton Cordasco)
144
+ - [@shazow](https://github.com/shazow) (Andrey Petrov)
145
+
146
+ 👋
147
+
148
+
149
+ ## Sponsorship
150
+
151
+ If your company benefits from this library, please consider [sponsoring its
152
+ development](https://urllib3.readthedocs.io/en/latest/sponsors.html).
153
+
154
+
155
+ ## For Enterprise
156
+
157
+ Professional support for urllib3 is available as part of the [Tidelift
158
+ Subscription][1]. Tidelift gives software development teams a single source for
159
+ purchasing and maintaining their software, with professional grade assurances
160
+ from the experts who know it best, while seamlessly integrating with existing
161
+ tools.
162
+
163
+ [1]: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme
.venv/Lib/site-packages/urllib3-2.7.0.dist-info/RECORD ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ urllib3-2.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ urllib3-2.7.0.dist-info/METADATA,sha256=ZhR4VtMLu2vtAcbOMybQ9I2E7V8oasF3YzoUhrgurOU,6852
3
+ urllib3-2.7.0.dist-info/RECORD,,
4
+ urllib3-2.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ urllib3-2.7.0.dist-info/licenses/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093
6
+ urllib3/__init__.py,sha256=JMo1tg1nIV1AeJ2vENC_Txfl0e5h6Gzl9DGVk1rWRbo,6979
7
+ urllib3/__pycache__/__init__.cpython-312.pyc,,
8
+ urllib3/__pycache__/_base_connection.cpython-312.pyc,,
9
+ urllib3/__pycache__/_collections.cpython-312.pyc,,
10
+ urllib3/__pycache__/_request_methods.cpython-312.pyc,,
11
+ urllib3/__pycache__/_version.cpython-312.pyc,,
12
+ urllib3/__pycache__/connection.cpython-312.pyc,,
13
+ urllib3/__pycache__/connectionpool.cpython-312.pyc,,
14
+ urllib3/__pycache__/exceptions.cpython-312.pyc,,
15
+ urllib3/__pycache__/fields.cpython-312.pyc,,
16
+ urllib3/__pycache__/filepost.cpython-312.pyc,,
17
+ urllib3/__pycache__/poolmanager.cpython-312.pyc,,
18
+ urllib3/__pycache__/response.cpython-312.pyc,,
19
+ urllib3/_base_connection.py,sha256=HzcSEHexgDrRUr60jNniB2KQjdw97SedD5luRfHtCXg,5580
20
+ urllib3/_collections.py,sha256=aOVm2mKilvuvT1efAGtkA7pi65C1NC3HJfpFxvYBS8A,17522
21
+ urllib3/_request_methods.py,sha256=gCeF85SO_UU4WoPwYHIoz_tw-eM_EVOkLFp8OFsC7DA,9931
22
+ urllib3/_version.py,sha256=-gMrKhsPiOj0XzUezVFa59d1S6QgQiKgQT5gGgyM8-w,520
23
+ urllib3/connection.py,sha256=Zos3qxKDW9-GQ6aVqfBQVRM5soB0IjHxY_xXWpJaFTI,42786
24
+ urllib3/connectionpool.py,sha256=sGFnddXYwlx7KC4JCP1gKvdNGLNK-YTCJDdGACGj3Y8,44164
25
+ urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ urllib3/contrib/__pycache__/__init__.cpython-312.pyc,,
27
+ urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc,,
28
+ urllib3/contrib/__pycache__/socks.cpython-312.pyc,,
29
+ urllib3/contrib/emscripten/__init__.py,sha256=wyXve8rmqX7s2KqRQBxD5Wl48jzWPn5-1u_XoQBELVc,836
30
+ urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc,,
31
+ urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc,,
32
+ urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc,,
33
+ urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc,,
34
+ urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc,,
35
+ urllib3/contrib/emscripten/connection.py,sha256=giElsBoUsKVURbZzb8GCrJmqW23Xnvj2aNyQVF42slg,8960
36
+ urllib3/contrib/emscripten/emscripten_fetch_worker.js,sha256=z1k3zZ4_hDKd3-tN7wzz8LHjHC2pxN_uu8B3k9D9A3c,3677
37
+ urllib3/contrib/emscripten/fetch.py,sha256=5xcd--viFxZd2nBy0aK73dtJ9Tsh1yYZU_SUXwnwibk,23520
38
+ urllib3/contrib/emscripten/request.py,sha256=mL28szy1KvE3NJhWor5jNmarp8gwplDU-7gwGZY5g0Q,566
39
+ urllib3/contrib/emscripten/response.py,sha256=CDpY0GFoluR3IxECkM2gH5uDfYDM0EL1FWr7B76wtgM,9719
40
+ urllib3/contrib/pyopenssl.py,sha256=XZY-QzyT7s8d43qisA4o-eSZYBeIkPyBMZhtj2kkLZs,19734
41
+ urllib3/contrib/socks.py,sha256=rSuklfha4-vto-8qSLhSPmf5lmRPIYuqdAxKe9bg2RA,7639
42
+ urllib3/exceptions.py,sha256=hQPHoqo4yw46esNSVkciVQXVUgAxWAglsh6MbyQta-Q,9945
43
+ urllib3/fields.py,sha256=aGLFAVZpVU-FbJlllve4Ahg0-pH9ZZBQ2Iz7lfpkjkM,10801
44
+ urllib3/filepost.py,sha256=U8eNZ-mpKKHhrlbHEEiTxxgK16IejhEa7uz42yqA_dI,2388
45
+ urllib3/http2/__init__.py,sha256=xzrASH7R5ANRkPJOot5lGnATOq3KKuyXzI42rcnwmqs,1741
46
+ urllib3/http2/__pycache__/__init__.cpython-312.pyc,,
47
+ urllib3/http2/__pycache__/connection.cpython-312.pyc,,
48
+ urllib3/http2/__pycache__/probe.cpython-312.pyc,,
49
+ urllib3/http2/connection.py,sha256=bHMH6fNvatwXPrKqrcn74yA3pUWcqPDppnK1LcKCbP8,12578
50
+ urllib3/http2/probe.py,sha256=nnAkqbhAakOiF75rz7W0udZ38Eeh_uD8fjV74N73FEI,3014
51
+ urllib3/poolmanager.py,sha256=c0rh0rcUC1t5tDGuUeGIgAzlErasPOwWwLiB67lX8pM,23895
52
+ urllib3/py.typed,sha256=UaCuPFa3H8UAakbt-5G8SPacldTOGvJv18pPjUJ5gDY,93
53
+ urllib3/response.py,sha256=9SX4BkkdoLgsx1ne6hpt-5PncrnDzPzeqC1DaShSc-M,53219
54
+ urllib3/util/__init__.py,sha256=-qeS0QceivazvBEKDNFCAI-6ACcdDOE4TMvo7SLNlAQ,1001
55
+ urllib3/util/__pycache__/__init__.cpython-312.pyc,,
56
+ urllib3/util/__pycache__/connection.cpython-312.pyc,,
57
+ urllib3/util/__pycache__/proxy.cpython-312.pyc,,
58
+ urllib3/util/__pycache__/request.cpython-312.pyc,,
59
+ urllib3/util/__pycache__/response.cpython-312.pyc,,
60
+ urllib3/util/__pycache__/retry.cpython-312.pyc,,
61
+ urllib3/util/__pycache__/ssl_.cpython-312.pyc,,
62
+ urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc,,
63
+ urllib3/util/__pycache__/ssltransport.cpython-312.pyc,,
64
+ urllib3/util/__pycache__/timeout.cpython-312.pyc,,
65
+ urllib3/util/__pycache__/url.cpython-312.pyc,,
66
+ urllib3/util/__pycache__/util.cpython-312.pyc,,
67
+ urllib3/util/__pycache__/wait.cpython-312.pyc,,
68
+ urllib3/util/connection.py,sha256=JjO722lzHlzLXPTkr9ZWBdhseXnMVjMSb1DJLVrXSnQ,4444
69
+ urllib3/util/proxy.py,sha256=seP8-Q5B6bB0dMtwPj-YcZZQ30vHuLqRu-tI0JZ2fzs,1148
70
+ urllib3/util/request.py,sha256=itpnC8ug7D4nVfDmGUCRMlgkARUQ13r_XMxSnzTwmpE,8363
71
+ urllib3/util/response.py,sha256=vQE639uoEhj1vpjEdxu5lNIhJCSUZkd7pqllUI0BZOA,3374
72
+ urllib3/util/retry.py,sha256=2YnSX-_FecMShD61Mx5s68J0_btUHZrrc_BkFVRS1P4,19577
73
+ urllib3/util/ssl_.py,sha256=Oqe3rIhUU3e3GVgZob2hxmR8Q0ZDhrhESPlPP_GLaVQ,17742
74
+ urllib3/util/ssl_match_hostname.py,sha256=Ft44KJzTzGMmKff_ZXP91li2V4WhmvEy16PKBcv4vZk,5479
75
+ urllib3/util/ssltransport.py,sha256=Ez4O8pR_vT8dan_FvqBYS6dgDfBXEMfVfrzcdUoWfi4,8847
76
+ urllib3/util/timeout.py,sha256=4eT1FVeZZU7h7mYD1Jq2OXNe4fxekdNvhoWUkZusRpA,10346
77
+ urllib3/util/url.py,sha256=WRh-TMYXosmgp8m8lT4H5spoHw5yUjlcMCfU53AkoAs,15205
78
+ urllib3/util/util.py,sha256=j3lbZK1jPyiwD34T8IgJzdWEZVT-4E-0vYIJi9UjeNA,1146
79
+ urllib3/util/wait.py,sha256=_ph8IrUR3sqPqi0OopQgJUlH4wzkGeM5CiyA7XGGtmI,4423
.venv/Lib/site-packages/urllib3-2.7.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
.venv/Lib/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2008-2020 Andrey Petrov and contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
.venv/Lib/site-packages/urllib3/util/wait.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import select
4
+ import socket
5
+ from functools import partial
6
+
7
+ __all__ = ["wait_for_read", "wait_for_write"]
8
+
9
+
10
+ # How should we wait on sockets?
11
+ #
12
+ # There are two types of APIs you can use for waiting on sockets: the fancy
13
+ # modern stateful APIs like epoll/kqueue, and the older stateless APIs like
14
+ # select/poll. The stateful APIs are more efficient when you have a lots of
15
+ # sockets to keep track of, because you can set them up once and then use them
16
+ # lots of times. But we only ever want to wait on a single socket at a time
17
+ # and don't want to keep track of state, so the stateless APIs are actually
18
+ # more efficient. So we want to use select() or poll().
19
+ #
20
+ # Now, how do we choose between select() and poll()? On traditional Unixes,
21
+ # select() has a strange calling convention that makes it slow, or fail
22
+ # altogether, for high-numbered file descriptors. The point of poll() is to fix
23
+ # that, so on Unixes, we prefer poll().
24
+ #
25
+ # On Windows, there is no poll() (or at least Python doesn't provide a wrapper
26
+ # for it), but that's OK, because on Windows, select() doesn't have this
27
+ # strange calling convention; plain select() works fine.
28
+ #
29
+ # So: on Windows we use select(), and everywhere else we use poll(). We also
30
+ # fall back to select() in case poll() is somehow broken or missing.
31
+
32
+
33
+ def select_wait_for_socket(
34
+ sock: socket.socket,
35
+ read: bool = False,
36
+ write: bool = False,
37
+ timeout: float | None = None,
38
+ ) -> bool:
39
+ if not read and not write:
40
+ raise RuntimeError("must specify at least one of read=True, write=True")
41
+ rcheck = []
42
+ wcheck = []
43
+ if read:
44
+ rcheck.append(sock)
45
+ if write:
46
+ wcheck.append(sock)
47
+ # When doing a non-blocking connect, most systems signal success by
48
+ # marking the socket writable. Windows, though, signals success by marked
49
+ # it as "exceptional". We paper over the difference by checking the write
50
+ # sockets for both conditions. (The stdlib selectors module does the same
51
+ # thing.)
52
+ fn = partial(select.select, rcheck, wcheck, wcheck)
53
+ rready, wready, xready = fn(timeout)
54
+ return bool(rready or wready or xready)
55
+
56
+
57
+ def poll_wait_for_socket(
58
+ sock: socket.socket,
59
+ read: bool = False,
60
+ write: bool = False,
61
+ timeout: float | None = None,
62
+ ) -> bool:
63
+ if not read and not write:
64
+ raise RuntimeError("must specify at least one of read=True, write=True")
65
+ mask = 0
66
+ if read:
67
+ mask |= select.POLLIN
68
+ if write:
69
+ mask |= select.POLLOUT
70
+ poll_obj = select.poll()
71
+ poll_obj.register(sock, mask)
72
+
73
+ # For some reason, poll() takes timeout in milliseconds
74
+ def do_poll(t: float | None) -> list[tuple[int, int]]:
75
+ if t is not None:
76
+ t *= 1000
77
+ return poll_obj.poll(t)
78
+
79
+ return bool(do_poll(timeout))
80
+
81
+
82
+ def _have_working_poll() -> bool:
83
+ # Apparently some systems have a select.poll that fails as soon as you try
84
+ # to use it, either due to strange configuration or broken monkeypatching
85
+ # from libraries like eventlet/greenlet.
86
+ try:
87
+ poll_obj = select.poll()
88
+ poll_obj.poll(0)
89
+ except (AttributeError, OSError):
90
+ return False
91
+ else:
92
+ return True
93
+
94
+
95
+ def wait_for_socket(
96
+ sock: socket.socket,
97
+ read: bool = False,
98
+ write: bool = False,
99
+ timeout: float | None = None,
100
+ ) -> bool:
101
+ # We delay choosing which implementation to use until the first time we're
102
+ # called. We could do it at import time, but then we might make the wrong
103
+ # decision if someone goes wild with monkeypatching select.poll after
104
+ # we're imported.
105
+ global wait_for_socket
106
+ if _have_working_poll():
107
+ wait_for_socket = poll_wait_for_socket
108
+ elif hasattr(select, "select"):
109
+ wait_for_socket = select_wait_for_socket
110
+ return wait_for_socket(sock, read, write, timeout)
111
+
112
+
113
+ def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool:
114
+ """Waits for reading to be available on a given socket.
115
+ Returns True if the socket is readable, or False if the timeout expired.
116
+ """
117
+ return wait_for_socket(sock, read=True, timeout=timeout)
118
+
119
+
120
+ def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool:
121
+ """Waits for writing to be available on a given socket.
122
+ Returns True if the socket is readable, or False if the timeout expired.
123
+ """
124
+ return wait_for_socket(sock, write=True, timeout=timeout)
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/METADATA ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: uvicorn
3
+ Version: 0.51.0
4
+ Summary: The lightning-fast ASGI server.
5
+ Project-URL: Changelog, https://uvicorn.dev/release-notes
6
+ Project-URL: Funding, https://github.com/sponsors/encode
7
+ Project-URL: Homepage, https://uvicorn.dev/
8
+ Project-URL: Source, https://github.com/Kludex/uvicorn
9
+ Author-email: Tom Christie <tom@tomchristie.com>
10
+ Maintainer-email: Marcelo Trylesinski <marcelotryle@gmail.com>
11
+ License-Expression: BSD-3-Clause
12
+ License-File: LICENSE.md
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: Implementation :: CPython
24
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: click>=7.0
28
+ Requires-Dist: h11>=0.8
29
+ Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
30
+ Provides-Extra: standard
31
+ Requires-Dist: httptools>=0.8.0; extra == 'standard'
32
+ Requires-Dist: python-dotenv>=0.13; extra == 'standard'
33
+ Requires-Dist: pyyaml>=5.1; extra == 'standard'
34
+ Requires-Dist: uvloop>=0.15.1; (sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')) and extra == 'standard'
35
+ Requires-Dist: watchfiles>=0.20; extra == 'standard'
36
+ Requires-Dist: websockets>=13.0; extra == 'standard'
37
+ Description-Content-Type: text/markdown
38
+
39
+ <p align="center">
40
+ <img width="320" height="320" src="https://raw.githubusercontent.com/tomchristie/uvicorn/main/docs/uvicorn.png" alt='uvicorn'>
41
+ </p>
42
+
43
+ <p align="center">
44
+ <em>An ASGI web server, for Python.</em>
45
+ </p>
46
+
47
+ ---
48
+
49
+ [![Build Status](https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/uvicorn/actions)
50
+ [![Package version](https://badge.fury.io/py/uvicorn.svg)](https://pypi.python.org/pypi/uvicorn)
51
+ [![Supported Python Version](https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058)](https://pypi.org/project/uvicorn)
52
+ [![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs)
53
+
54
+ ---
55
+
56
+ **Documentation**: [https://uvicorn.dev](https://uvicorn.dev)
57
+
58
+ **Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn)
59
+
60
+ ---
61
+
62
+ Uvicorn is an ASGI web server implementation for Python.
63
+
64
+ Until recently Python has lacked a minimal low-level server/application interface for
65
+ async frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to
66
+ start building a common set of tooling usable across all async frameworks.
67
+
68
+ Uvicorn supports HTTP/1.1 and WebSockets.
69
+
70
+ ## Quickstart
71
+
72
+ Install using `pip`:
73
+
74
+ ```shell
75
+ $ pip install uvicorn
76
+ ```
77
+
78
+ This will install uvicorn with minimal (pure Python) dependencies.
79
+
80
+ ```shell
81
+ $ pip install 'uvicorn[standard]'
82
+ ```
83
+
84
+ This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras".
85
+
86
+ In this context, "Cython-based" means the following:
87
+
88
+ - the event loop `uvloop` will be installed and used if possible.
89
+ - the http protocol will be handled by `httptools` if possible.
90
+
91
+ Moreover, "optional extras" means that:
92
+
93
+ - the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible.
94
+ - the `--reload` flag in development mode will use `watchfiles`.
95
+ - `python-dotenv` will be installed should you want to use the `--env-file` option.
96
+ - `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired.
97
+
98
+ Create an application, in `example.py`:
99
+
100
+ ```python
101
+ async def app(scope, receive, send):
102
+ assert scope['type'] == 'http'
103
+
104
+ await send({
105
+ 'type': 'http.response.start',
106
+ 'status': 200,
107
+ 'headers': [
108
+ (b'content-type', b'text/plain'),
109
+ ],
110
+ })
111
+ await send({
112
+ 'type': 'http.response.body',
113
+ 'body': b'Hello, world!',
114
+ })
115
+ ```
116
+
117
+ Run the server:
118
+
119
+ ```shell
120
+ $ uvicorn example:app
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Why ASGI?
126
+
127
+ Most well established Python Web frameworks started out as WSGI-based frameworks.
128
+
129
+ WSGI applications are a single, synchronous callable that takes a request and returns a response.
130
+ This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections,
131
+ which WSGI doesn't support well.
132
+
133
+ Having an async concurrency model also allows for options such as lightweight background tasks,
134
+ and can be less of a limiting factor for endpoints that have long periods being blocked on network
135
+ I/O such as dealing with slow HTTP requests.
136
+
137
+ ---
138
+
139
+ ## Alternative ASGI servers
140
+
141
+ A strength of the ASGI protocol is that it decouples the server implementation
142
+ from the application framework. This allows for an ecosystem of interoperating
143
+ webservers and application frameworks.
144
+
145
+ ### Daphne
146
+
147
+ The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne].
148
+
149
+ It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets.
150
+
151
+ Any of the example applications given here can equally well be run using `daphne` instead.
152
+
153
+ ```
154
+ $ pip install daphne
155
+ $ daphne app:App
156
+ ```
157
+
158
+ ### Hypercorn
159
+
160
+ [Hypercorn][hypercorn] was initially part of the Quart web framework, before
161
+ being separated out into a standalone ASGI server.
162
+
163
+ Hypercorn supports HTTP/1.1, HTTP/2, and WebSockets.
164
+
165
+ It also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`.
166
+
167
+ ```
168
+ $ pip install hypercorn
169
+ $ hypercorn app:App
170
+ ```
171
+
172
+ ### Mangum
173
+
174
+ [Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway.
175
+
176
+ ### Granian
177
+
178
+ [Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets.
179
+
180
+ ---
181
+
182
+ <p align="center"><i>Uvicorn is <a href="https://github.com/Kludex/uvicorn/blob/main/LICENSE.md">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>&mdash; 🦄 &mdash;</p>
183
+
184
+ [asgi]: https://asgi.readthedocs.io/en/latest/
185
+ [daphne]: https://github.com/django/daphne
186
+ [hypercorn]: https://github.com/pgjones/hypercorn
187
+ [trio]: https://trio.readthedocs.io
188
+ [mangum]: https://github.com/jordaneremieff/mangum
189
+ [granian]: https://github.com/emmett-framework/granian
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/RECORD ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ../../Scripts/uvicorn.exe,sha256=-SPhVkmoKomyN2m_cAAR7z-HvoWGTKbiaz4lKy7BacU,108462
2
+ uvicorn-0.51.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
+ uvicorn-0.51.0.dist-info/METADATA,sha256=B8cHkMDN5oEviRaPeqfBhYAKaQ9-ZX37VgOEjBN1AX4,6589
4
+ uvicorn-0.51.0.dist-info/RECORD,,
5
+ uvicorn-0.51.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
6
+ uvicorn-0.51.0.dist-info/entry_points.txt,sha256=FW1w-hkc9QgwaGoovMvm0ZY73w_NcycWdGAUfDsNGxw,46
7
+ uvicorn-0.51.0.dist-info/licenses/LICENSE.md,sha256=7-Gs8-YvuZwoiw7HPlp3O3Jo70Mg_nV-qZQhTktjw3E,1526
8
+ uvicorn/__init__.py,sha256=07oX9VGq6TnIs3mghqQk0uZIIdF08SJFBE0JXx3ps9g,147
9
+ uvicorn/__main__.py,sha256=DQizy6nKP0ywhPpnCHgmRDYIMfcqZKVEzNIWQZjqtVQ,62
10
+ uvicorn/__pycache__/__init__.cpython-312.pyc,,
11
+ uvicorn/__pycache__/__main__.cpython-312.pyc,,
12
+ uvicorn/__pycache__/_ansi.cpython-312.pyc,,
13
+ uvicorn/__pycache__/_compat.cpython-312.pyc,,
14
+ uvicorn/__pycache__/_subprocess.cpython-312.pyc,,
15
+ uvicorn/__pycache__/_types.cpython-312.pyc,,
16
+ uvicorn/__pycache__/config.cpython-312.pyc,,
17
+ uvicorn/__pycache__/importer.cpython-312.pyc,,
18
+ uvicorn/__pycache__/logging.cpython-312.pyc,,
19
+ uvicorn/__pycache__/main.cpython-312.pyc,,
20
+ uvicorn/__pycache__/server.cpython-312.pyc,,
21
+ uvicorn/__pycache__/workers.cpython-312.pyc,,
22
+ uvicorn/_ansi.py,sha256=s8RPpmPTO6QGRgEYbsNGeasSWqiEnCsTroyA8Z59Q00,617
23
+ uvicorn/_compat.py,sha256=6X49c9ovzmMHir2S3syinOQMzSPBcp8MNuI9ZQrbYiU,2916
24
+ uvicorn/_subprocess.py,sha256=fQ4JTajuXay6mVc8KQNGfXu6JHoab46aS6kyIMWJMPk,2776
25
+ uvicorn/_types.py,sha256=JDM6E-hqZ6o_cSZvjDULjAv77PFH2pLe_Vgqshe8qH4,7677
26
+ uvicorn/config.py,sha256=hjwBd2_PcHz92eeTvRgtbWIEvWKhMCITPT2M7fy0o68,24495
27
+ uvicorn/importer.py,sha256=nRt0QQ3qpi264-n_mR0l55C2ddM8nowTNzT1jsWaam8,1128
28
+ uvicorn/lifespan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc,,
30
+ uvicorn/lifespan/__pycache__/off.cpython-312.pyc,,
31
+ uvicorn/lifespan/__pycache__/on.cpython-312.pyc,,
32
+ uvicorn/lifespan/off.py,sha256=nfI6qHAUo_8-BEXMBKoHQ9wUbsXrPaXLCbDSS0vKSr8,332
33
+ uvicorn/lifespan/on.py,sha256=iETPFj6SFhPDPcdeGxfa43LKImJU7MI5i7QUUepBcPE,5173
34
+ uvicorn/logging.py,sha256=kCB1n45B5O55SSApiv2x6UQG3LnFqNlGvcohGyAwblU,4256
35
+ uvicorn/loops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ uvicorn/loops/__pycache__/__init__.cpython-312.pyc,,
37
+ uvicorn/loops/__pycache__/asyncio.cpython-312.pyc,,
38
+ uvicorn/loops/__pycache__/auto.cpython-312.pyc,,
39
+ uvicorn/loops/__pycache__/uvloop.cpython-312.pyc,,
40
+ uvicorn/loops/asyncio.py,sha256=a8wO4fB4pCk-RLICq7CnGr_cxQI7MBLJj-Go4UQldD8,333
41
+ uvicorn/loops/auto.py,sha256=pdTsMcqkbE8fvdNgLs1y4c6hsuN598mZOSOlC0a23S4,566
42
+ uvicorn/loops/uvloop.py,sha256=Ca-TL-W8tbdEW5uvL_GrMih7G8e_t_vhNevi2NKYjEU,236
43
+ uvicorn/main.py,sha256=YitRjBbr9ECYXUYzV5_y0tVud-8RfkGXrk9dnJJqndg,18812
44
+ uvicorn/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ uvicorn/middleware/__pycache__/__init__.cpython-312.pyc,,
46
+ uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc,,
47
+ uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc,,
48
+ uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc,,
49
+ uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc,,
50
+ uvicorn/middleware/asgi2.py,sha256=YQrQNm3RehFts3mzk3k4yw8aD8Egtj0tRS3N45YkQa0,394
51
+ uvicorn/middleware/message_logger.py,sha256=IHEZUSnFNaMFUFdwtZO3AuFATnYcSor-gVtOjbCzt8M,2859
52
+ uvicorn/middleware/proxy_headers.py,sha256=pNZk80NNscLEwi8pGn6cIbaF0bSfJhCKn3pHjqqfW90,7421
53
+ uvicorn/middleware/wsgi.py,sha256=VnbnkdCL3_ctLWzk1j39xvxysuoAB3kSYmi8TsOLpNs,7125
54
+ uvicorn/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ uvicorn/protocols/__pycache__/__init__.cpython-312.pyc,,
56
+ uvicorn/protocols/__pycache__/utils.cpython-312.pyc,,
57
+ uvicorn/protocols/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
+ uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc,,
59
+ uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc,,
60
+ uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc,,
61
+ uvicorn/protocols/http/__pycache__/h11_impl.cpython-312.pyc,,
62
+ uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc,,
63
+ uvicorn/protocols/http/auto.py,sha256=YfXGyzWTaaE2p_jkTPWrJCXsxEaQnC3NK0-G7Wgmnls,403
64
+ uvicorn/protocols/http/flow_control.py,sha256=050WVg31EvPOkHwynCoMP1zXFl_vO3U4durlc5vyp4U,1701
65
+ uvicorn/protocols/http/h11_impl.py,sha256=DBDE_HnnQtqW50bAb4OW3dKKDgEATUSOvNqfDCDBF2Q,21202
66
+ uvicorn/protocols/http/httptools_impl.py,sha256=Aq2MlNrL99irluvR673AfcVbo6qlogl8fiR9IODMxxA,22355
67
+ uvicorn/protocols/utils.py,sha256=1hKdDCmo_GtGrK1zZfa70jL7Hr3mDE8RnL69O4rHQRg,2058
68
+ uvicorn/protocols/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
+ uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc,,
70
+ uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc,,
71
+ uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc,,
72
+ uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-312.pyc,,
73
+ uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-312.pyc,,
74
+ uvicorn/protocols/websockets/auto.py,sha256=QOnMKZfXHfOcZHdq1OITHwv7Awx1BTS3eJfZswuGEU0,611
75
+ uvicorn/protocols/websockets/websockets_impl.py,sha256=7Bu_Ic_7AdxBcttG0GTLb3tzgQBsHx0Kyb_C49jBzxw,15176
76
+ uvicorn/protocols/websockets/websockets_sansio_impl.py,sha256=Tr54qVO78q0PiCUM-9-jnFabWahGZHLWa5ecKvkOsaY,20607
77
+ uvicorn/protocols/websockets/wsproto_impl.py,sha256=8088Bp8SOz4Z2L2gcqOqV3zgWDVxIN0eIkjhuR9oqYU,19821
78
+ uvicorn/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
79
+ uvicorn/server.py,sha256=ao_gfmmVQ_Ily8fQwAJ__Sb-yVeX1cahBEbTjHkp7Vc,13382
80
+ uvicorn/supervisors/__init__.py,sha256=wT8eOEIqT1yWQgytZtv5taWMul7xoTIY0xm1m4oyPTw,507
81
+ uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc,,
82
+ uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc,,
83
+ uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc,,
84
+ uvicorn/supervisors/__pycache__/statreload.cpython-312.pyc,,
85
+ uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc,,
86
+ uvicorn/supervisors/basereload.py,sha256=C-YD18flOKKVRNTRsSCOEvyqGf3ZxpvuUvR0z0Y1Wpg,4018
87
+ uvicorn/supervisors/multiprocess.py,sha256=U1QZdiJ_7Auazix6XOj1ku0Nj_N_PJPCoYuq6SNjZcI,10271
88
+ uvicorn/supervisors/statreload.py,sha256=XPaEtVXaasII1kkG32UUG8oD7-fS0FwyXkQLF9QYPbA,1548
89
+ uvicorn/supervisors/watchfilesreload.py,sha256=u-MuYm0LsZMN670u_TtSWiMBUA2z4j1sIe3O2A67Ep4,2923
90
+ uvicorn/workers.py,sha256=lMCubn1Wi-4h2jxrXNMmhOJZy7ZP8QxbZbP3_2O1iHE,3873
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ uvicorn = uvicorn.main:main
.venv/Lib/site-packages/uvicorn-0.51.0.dist-info/licenses/LICENSE.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/).
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.venv/Lib/site-packages/uvicorn/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from uvicorn.config import Config
2
+ from uvicorn.main import Server, main, run
3
+
4
+ __version__ = "0.51.0"
5
+ __all__ = ["main", "run", "Config", "Server"]
.venv/Lib/site-packages/uvicorn/__main__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import uvicorn
2
+
3
+ if __name__ == "__main__":
4
+ uvicorn.main()
.venv/Lib/site-packages/uvicorn/_ansi.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ _ANSI_COLORS = {
4
+ "black": 30,
5
+ "red": 31,
6
+ "green": 32,
7
+ "yellow": 33,
8
+ "blue": 34,
9
+ "magenta": 35,
10
+ "cyan": 36,
11
+ "white": 37,
12
+ "bright_black": 90,
13
+ "bright_red": 91,
14
+ "bright_green": 92,
15
+ "bright_yellow": 93,
16
+ "bright_blue": 94,
17
+ "bright_magenta": 95,
18
+ "bright_cyan": 96,
19
+ "bright_white": 97,
20
+ }
21
+
22
+
23
+ def style(text: str, fg: str | None = None, bold: bool = False) -> str:
24
+ prefix = ""
25
+ if fg is not None:
26
+ prefix += f"\033[{_ANSI_COLORS[fg]}m"
27
+ if bold:
28
+ prefix += "\033[1m"
29
+ return f"{prefix}{text}\033[0m"
.venv/Lib/site-packages/uvicorn/_compat.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import sys
5
+ from collections.abc import Callable, Coroutine
6
+ from typing import Any, TypeVar
7
+
8
+ __all__ = ["asyncio_run", "iscoroutinefunction"]
9
+
10
+ if sys.version_info >= (3, 14):
11
+ from inspect import iscoroutinefunction
12
+ else:
13
+ from asyncio import iscoroutinefunction
14
+
15
+ _T = TypeVar("_T")
16
+
17
+ if sys.version_info >= (3, 12):
18
+ asyncio_run = asyncio.run
19
+ elif sys.version_info >= (3, 11):
20
+
21
+ def asyncio_run(
22
+ main: Coroutine[Any, Any, _T],
23
+ *,
24
+ debug: bool = False,
25
+ loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,
26
+ ) -> _T:
27
+ # asyncio.run from Python 3.12
28
+ # https://docs.python.org/3/license.html#psf-license
29
+ with asyncio.Runner(debug=debug, loop_factory=loop_factory) as runner:
30
+ return runner.run(main)
31
+
32
+ else:
33
+ # modified version of asyncio.run from Python 3.10 to add loop_factory kwarg
34
+ # https://docs.python.org/3/license.html#psf-license
35
+ def asyncio_run(
36
+ main: Coroutine[Any, Any, _T],
37
+ *,
38
+ debug: bool = False,
39
+ loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,
40
+ ) -> _T:
41
+ try:
42
+ asyncio.get_running_loop()
43
+ except RuntimeError:
44
+ pass
45
+ else:
46
+ raise RuntimeError("asyncio.run() cannot be called from a running event loop")
47
+
48
+ if not asyncio.iscoroutine(main):
49
+ raise ValueError(f"a coroutine was expected, got {main!r}")
50
+
51
+ if loop_factory is None:
52
+ loop = asyncio.new_event_loop()
53
+ else:
54
+ loop = loop_factory()
55
+ try:
56
+ if loop_factory is None:
57
+ asyncio.set_event_loop(loop)
58
+ if debug is not None:
59
+ loop.set_debug(debug)
60
+ return loop.run_until_complete(main)
61
+ finally:
62
+ try:
63
+ _cancel_all_tasks(loop)
64
+ loop.run_until_complete(loop.shutdown_asyncgens())
65
+ loop.run_until_complete(loop.shutdown_default_executor())
66
+ finally:
67
+ if loop_factory is None:
68
+ asyncio.set_event_loop(None)
69
+ loop.close()
70
+
71
+ def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None:
72
+ to_cancel = asyncio.all_tasks(loop)
73
+ if not to_cancel:
74
+ return
75
+
76
+ for task in to_cancel:
77
+ task.cancel()
78
+
79
+ loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
80
+
81
+ for task in to_cancel:
82
+ if task.cancelled():
83
+ continue
84
+ if task.exception() is not None:
85
+ loop.call_exception_handler(
86
+ {
87
+ "message": "unhandled exception during asyncio.run() shutdown",
88
+ "exception": task.exception(),
89
+ "task": task,
90
+ }
91
+ )
.venv/Lib/site-packages/uvicorn/_subprocess.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Some light wrappers around Python's multiprocessing, to deal with cleanly
3
+ starting child processes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import multiprocessing
9
+ import os
10
+ import sys
11
+ from collections.abc import Callable
12
+ from multiprocessing.context import SpawnProcess
13
+ from socket import socket
14
+
15
+ from uvicorn.config import Config
16
+
17
+ multiprocessing.allow_connection_pickling()
18
+ spawn = multiprocessing.get_context("spawn")
19
+
20
+
21
+ def get_subprocess(
22
+ config: Config,
23
+ target: Callable[..., None],
24
+ sockets: list[socket],
25
+ ) -> SpawnProcess:
26
+ """
27
+ Called in the parent process, to instantiate a new child process instance.
28
+ The child is not yet started at this point.
29
+
30
+ * config - The Uvicorn configuration instance.
31
+ * target - A callable that accepts a list of sockets. In practice this will
32
+ be the `Server.run()` method.
33
+ * sockets - A list of sockets to pass to the server. Sockets are bound once
34
+ by the parent process, and then passed to the child processes.
35
+ """
36
+ # We pass across the stdin fileno, and reopen it in the child process.
37
+ # This is required for some debugging environments.
38
+ try:
39
+ stdin_fileno = sys.stdin.fileno()
40
+ # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.
41
+ except (AttributeError, OSError):
42
+ stdin_fileno = None
43
+
44
+ kwargs = {
45
+ "config": config,
46
+ "target": target,
47
+ "sockets": sockets,
48
+ "stdin_fileno": stdin_fileno,
49
+ }
50
+
51
+ return spawn.Process(target=subprocess_started, kwargs=kwargs)
52
+
53
+
54
+ def subprocess_started(
55
+ config: Config,
56
+ target: Callable[..., None],
57
+ sockets: list[socket],
58
+ stdin_fileno: int | None,
59
+ ) -> None:
60
+ """
61
+ Called when the child process starts.
62
+
63
+ * config - The Uvicorn configuration instance.
64
+ * target - A callable that accepts a list of sockets. In practice this will
65
+ be the `Server.run()` method.
66
+ * sockets - A list of sockets to pass to the server. Sockets are bound once
67
+ by the parent process, and then passed to the child processes.
68
+ * stdin_fileno - The file number of sys.stdin, so that it can be reattached
69
+ to the child process.
70
+ """
71
+ # Re-open stdin.
72
+ if stdin_fileno is not None:
73
+ sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage
74
+
75
+ # Logging needs to be setup again for each child.
76
+ config.configure_logging()
77
+
78
+ try:
79
+ # Now we can call into `Server.run(sockets=sockets)`
80
+ target(sockets=sockets)
81
+ except KeyboardInterrupt: # pragma: no cover
82
+ # suppress the exception to avoid a traceback from subprocess.Popen
83
+ # the parent already expects us to end, so no vital information is lost
84
+ pass
.venv/Lib/site-packages/uvicorn/_types.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copyright (c) Django Software Foundation and individual contributors.
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of Django nor the names of its contributors may be used
16
+ to endorse or promote products derived from this software without
17
+ specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import sys
34
+ import types
35
+ from collections.abc import Awaitable, Callable, Iterable, MutableMapping
36
+ from typing import Any, Literal, Protocol, TypedDict
37
+
38
+ if sys.version_info >= (3, 11): # pragma: py-lt-311
39
+ from typing import NotRequired
40
+ else: # pragma: py-gte-311
41
+ from typing_extensions import NotRequired
42
+
43
+ # WSGI
44
+ Environ = MutableMapping[str, Any]
45
+ ExcInfo = tuple[type[BaseException], BaseException, types.TracebackType | None]
46
+ StartResponse = Callable[[str, Iterable[tuple[str, str]], ExcInfo | None], None]
47
+ WSGIApp = Callable[[Environ, StartResponse], Iterable[bytes] | BaseException]
48
+
49
+
50
+ # ASGI
51
+ class ASGIVersions(TypedDict):
52
+ spec_version: str
53
+ version: Literal["2.0"] | Literal["3.0"]
54
+
55
+
56
+ class HTTPScope(TypedDict):
57
+ type: Literal["http"]
58
+ asgi: ASGIVersions
59
+ http_version: str
60
+ method: str
61
+ scheme: str
62
+ path: str
63
+ raw_path: bytes
64
+ query_string: bytes
65
+ root_path: str
66
+ headers: Iterable[tuple[bytes, bytes]]
67
+ client: tuple[str, int] | None
68
+ server: tuple[str, int | None] | None
69
+ state: NotRequired[dict[str, Any]]
70
+ extensions: NotRequired[dict[str, dict[object, object]]]
71
+
72
+
73
+ class WebSocketScope(TypedDict):
74
+ type: Literal["websocket"]
75
+ asgi: ASGIVersions
76
+ http_version: str
77
+ scheme: str
78
+ path: str
79
+ raw_path: bytes
80
+ query_string: bytes
81
+ root_path: str
82
+ headers: Iterable[tuple[bytes, bytes]]
83
+ client: tuple[str, int] | None
84
+ server: tuple[str, int | None] | None
85
+ subprotocols: Iterable[str]
86
+ state: NotRequired[dict[str, Any]]
87
+ extensions: NotRequired[dict[str, dict[object, object]]]
88
+
89
+
90
+ class LifespanScope(TypedDict):
91
+ type: Literal["lifespan"]
92
+ asgi: ASGIVersions
93
+ state: NotRequired[dict[str, Any]]
94
+
95
+
96
+ WWWScope = HTTPScope | WebSocketScope
97
+ Scope = HTTPScope | WebSocketScope | LifespanScope
98
+
99
+
100
+ class HTTPRequestEvent(TypedDict):
101
+ type: Literal["http.request"]
102
+ body: bytes
103
+ more_body: bool
104
+
105
+
106
+ class HTTPResponseDebugEvent(TypedDict):
107
+ type: Literal["http.response.debug"]
108
+ info: dict[str, object]
109
+
110
+
111
+ class HTTPResponseStartEvent(TypedDict):
112
+ type: Literal["http.response.start"]
113
+ status: int
114
+ headers: NotRequired[Iterable[tuple[bytes, bytes]]]
115
+ trailers: NotRequired[bool]
116
+
117
+
118
+ class HTTPResponseBodyEvent(TypedDict):
119
+ type: Literal["http.response.body"]
120
+ body: bytes
121
+ more_body: NotRequired[bool]
122
+
123
+
124
+ class HTTPResponseTrailersEvent(TypedDict):
125
+ type: Literal["http.response.trailers"]
126
+ headers: Iterable[tuple[bytes, bytes]]
127
+ more_trailers: bool
128
+
129
+
130
+ class HTTPServerPushEvent(TypedDict):
131
+ type: Literal["http.response.push"]
132
+ path: str
133
+ headers: Iterable[tuple[bytes, bytes]]
134
+
135
+
136
+ class HTTPDisconnectEvent(TypedDict):
137
+ type: Literal["http.disconnect"]
138
+
139
+
140
+ class WebSocketConnectEvent(TypedDict):
141
+ type: Literal["websocket.connect"]
142
+
143
+
144
+ class WebSocketAcceptEvent(TypedDict):
145
+ type: Literal["websocket.accept"]
146
+ subprotocol: NotRequired[str | None]
147
+ headers: NotRequired[Iterable[tuple[bytes, bytes]]]
148
+
149
+
150
+ class _WebSocketReceiveEventBytes(TypedDict):
151
+ type: Literal["websocket.receive"]
152
+ bytes: bytes
153
+ text: NotRequired[None]
154
+
155
+
156
+ class _WebSocketReceiveEventText(TypedDict):
157
+ type: Literal["websocket.receive"]
158
+ bytes: NotRequired[None]
159
+ text: str
160
+
161
+
162
+ WebSocketReceiveEvent = _WebSocketReceiveEventBytes | _WebSocketReceiveEventText
163
+
164
+
165
+ class _WebSocketSendEventBytes(TypedDict):
166
+ type: Literal["websocket.send"]
167
+ bytes: bytes
168
+ text: NotRequired[None]
169
+
170
+
171
+ class _WebSocketSendEventText(TypedDict):
172
+ type: Literal["websocket.send"]
173
+ bytes: NotRequired[None]
174
+ text: str
175
+
176
+
177
+ WebSocketSendEvent = _WebSocketSendEventBytes | _WebSocketSendEventText
178
+
179
+
180
+ class WebSocketResponseStartEvent(TypedDict):
181
+ type: Literal["websocket.http.response.start"]
182
+ status: int
183
+ headers: Iterable[tuple[bytes, bytes]]
184
+
185
+
186
+ class WebSocketResponseBodyEvent(TypedDict):
187
+ type: Literal["websocket.http.response.body"]
188
+ body: bytes
189
+ more_body: NotRequired[bool]
190
+
191
+
192
+ class WebSocketDisconnectEvent(TypedDict):
193
+ type: Literal["websocket.disconnect"]
194
+ code: int
195
+ reason: NotRequired[str | None]
196
+
197
+
198
+ class WebSocketCloseEvent(TypedDict):
199
+ type: Literal["websocket.close"]
200
+ code: NotRequired[int]
201
+ reason: NotRequired[str | None]
202
+
203
+
204
+ class LifespanStartupEvent(TypedDict):
205
+ type: Literal["lifespan.startup"]
206
+
207
+
208
+ class LifespanShutdownEvent(TypedDict):
209
+ type: Literal["lifespan.shutdown"]
210
+
211
+
212
+ class LifespanStartupCompleteEvent(TypedDict):
213
+ type: Literal["lifespan.startup.complete"]
214
+
215
+
216
+ class LifespanStartupFailedEvent(TypedDict):
217
+ type: Literal["lifespan.startup.failed"]
218
+ message: str
219
+
220
+
221
+ class LifespanShutdownCompleteEvent(TypedDict):
222
+ type: Literal["lifespan.shutdown.complete"]
223
+
224
+
225
+ class LifespanShutdownFailedEvent(TypedDict):
226
+ type: Literal["lifespan.shutdown.failed"]
227
+ message: str
228
+
229
+
230
+ WebSocketEvent = WebSocketReceiveEvent | WebSocketDisconnectEvent | WebSocketConnectEvent
231
+
232
+
233
+ ASGIReceiveEvent = (
234
+ HTTPRequestEvent
235
+ | HTTPDisconnectEvent
236
+ | WebSocketConnectEvent
237
+ | WebSocketReceiveEvent
238
+ | WebSocketDisconnectEvent
239
+ | LifespanStartupEvent
240
+ | LifespanShutdownEvent
241
+ )
242
+
243
+
244
+ ASGISendEvent = (
245
+ HTTPResponseStartEvent
246
+ | HTTPResponseBodyEvent
247
+ | HTTPResponseTrailersEvent
248
+ | HTTPServerPushEvent
249
+ | HTTPDisconnectEvent
250
+ | WebSocketAcceptEvent
251
+ | WebSocketSendEvent
252
+ | WebSocketResponseStartEvent
253
+ | WebSocketResponseBodyEvent
254
+ | WebSocketCloseEvent
255
+ | LifespanStartupCompleteEvent
256
+ | LifespanStartupFailedEvent
257
+ | LifespanShutdownCompleteEvent
258
+ | LifespanShutdownFailedEvent
259
+ )
260
+
261
+
262
+ ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]]
263
+ ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]]
264
+
265
+
266
+ class ASGI2Protocol(Protocol):
267
+ def __init__(self, scope: Scope) -> None: ... # pragma: no cover
268
+
269
+ async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover
270
+
271
+
272
+ ASGI2Application = type[ASGI2Protocol]
273
+ ASGI3Application = Callable[[Scope, ASGIReceiveCallable, ASGISendCallable], Awaitable[None]]
274
+ ASGIApplication = ASGI2Application | ASGI3Application
.venv/Lib/site-packages/uvicorn/config.py ADDED
@@ -0,0 +1,600 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ import json
6
+ import logging
7
+ import logging.config
8
+ import os
9
+ import socket
10
+ import ssl
11
+ import sys
12
+ from collections.abc import Awaitable, Callable
13
+ from configparser import RawConfigParser
14
+ from pathlib import Path
15
+ from typing import IO, Any, Literal
16
+
17
+ from uvicorn._ansi import style
18
+ from uvicorn._compat import iscoroutinefunction
19
+ from uvicorn._types import ASGIApplication
20
+ from uvicorn.importer import ImportFromStringError, import_from_string
21
+ from uvicorn.logging import TRACE_LOG_LEVEL
22
+ from uvicorn.middleware.asgi2 import ASGI2Middleware
23
+ from uvicorn.middleware.message_logger import MessageLoggerMiddleware
24
+ from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
25
+ from uvicorn.middleware.wsgi import WSGIMiddleware
26
+
27
+
28
+ class UvicornDeprecationWarning(UserWarning):
29
+ """A custom deprecation warning for Uvicorn.
30
+
31
+ Unlike the built-in DeprecationWarning, this inherits from UserWarning to ensure it is visible by default, helping
32
+ users discover deprecated features without needing to enable warnings explicitly.
33
+
34
+ Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
35
+ """
36
+
37
+
38
+ HTTPProtocolType = Literal["auto", "h11", "httptools"]
39
+ WSProtocolType = Literal["auto", "none", "websockets", "websockets-sansio", "wsproto"]
40
+ LifespanType = Literal["auto", "on", "off"]
41
+ LoopFactoryType = Literal["none", "auto", "asyncio", "uvloop"]
42
+ InterfaceType = Literal["auto", "asgi3", "asgi2", "wsgi"]
43
+
44
+ LOG_LEVELS: dict[str, int] = {
45
+ "critical": logging.CRITICAL,
46
+ "error": logging.ERROR,
47
+ "warning": logging.WARNING,
48
+ "info": logging.INFO,
49
+ "debug": logging.DEBUG,
50
+ "trace": TRACE_LOG_LEVEL,
51
+ }
52
+ HTTP_PROTOCOLS: dict[str, str] = {
53
+ "auto": "uvicorn.protocols.http.auto:AutoHTTPProtocol",
54
+ "h11": "uvicorn.protocols.http.h11_impl:H11Protocol",
55
+ "httptools": "uvicorn.protocols.http.httptools_impl:HttpToolsProtocol",
56
+ }
57
+ WS_PROTOCOLS: dict[str, str | None] = {
58
+ "auto": "uvicorn.protocols.websockets.auto:AutoWebSocketsProtocol",
59
+ "none": None,
60
+ "websockets": "uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol",
61
+ "websockets-sansio": "uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol",
62
+ "wsproto": "uvicorn.protocols.websockets.wsproto_impl:WSProtocol",
63
+ }
64
+ LIFESPAN: dict[str, str] = {
65
+ "auto": "uvicorn.lifespan.on:LifespanOn",
66
+ "on": "uvicorn.lifespan.on:LifespanOn",
67
+ "off": "uvicorn.lifespan.off:LifespanOff",
68
+ }
69
+ LOOP_FACTORIES: dict[str, str | None] = {
70
+ "none": None,
71
+ "auto": "uvicorn.loops.auto:auto_loop_factory",
72
+ "asyncio": "uvicorn.loops.asyncio:asyncio_loop_factory",
73
+ "uvloop": "uvicorn.loops.uvloop:uvloop_loop_factory",
74
+ }
75
+ INTERFACES: list[InterfaceType] = ["auto", "asgi3", "asgi2", "wsgi"]
76
+
77
+ SSL_PROTOCOL_VERSION: int = ssl.PROTOCOL_TLS_SERVER
78
+
79
+ STARTUP_FAILURE = 3
80
+
81
+ LOGGING_CONFIG: dict[str, Any] = {
82
+ "version": 1,
83
+ "disable_existing_loggers": False,
84
+ "formatters": {
85
+ "default": {
86
+ "()": "uvicorn.logging.DefaultFormatter",
87
+ "fmt": "%(levelprefix)s %(message)s",
88
+ "use_colors": None,
89
+ },
90
+ "access": {
91
+ "()": "uvicorn.logging.AccessFormatter",
92
+ "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
93
+ },
94
+ },
95
+ "handlers": {
96
+ "default": {
97
+ "formatter": "default",
98
+ "class": "logging.StreamHandler",
99
+ "stream": "ext://sys.stderr",
100
+ },
101
+ "access": {
102
+ "formatter": "access",
103
+ "class": "logging.StreamHandler",
104
+ "stream": "ext://sys.stdout",
105
+ },
106
+ },
107
+ "loggers": {
108
+ "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
109
+ "uvicorn.error": {"level": "INFO"},
110
+ "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
111
+ },
112
+ }
113
+
114
+ logger = logging.getLogger("uvicorn.error")
115
+
116
+
117
+ def create_ssl_context(
118
+ certfile: str | os.PathLike[str],
119
+ keyfile: str | os.PathLike[str] | None,
120
+ password: str | None,
121
+ ssl_version: int,
122
+ cert_reqs: int,
123
+ ca_certs: str | os.PathLike[str] | None,
124
+ ciphers: str | None,
125
+ ) -> ssl.SSLContext:
126
+ ctx = ssl.SSLContext(ssl_version)
127
+ get_password = (lambda: password) if password else None
128
+ ctx.load_cert_chain(certfile, keyfile, get_password)
129
+ ctx.verify_mode = ssl.VerifyMode(cert_reqs)
130
+ if ca_certs:
131
+ ctx.load_verify_locations(ca_certs)
132
+ if ciphers:
133
+ ctx.set_ciphers(ciphers)
134
+ return ctx
135
+
136
+
137
+ def is_dir(path: Path) -> bool:
138
+ try:
139
+ if not path.is_absolute():
140
+ path = path.resolve()
141
+ return path.is_dir()
142
+ except OSError: # pragma: full coverage
143
+ return False
144
+
145
+
146
+ def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]:
147
+ directories: list[Path] = list(set(map(Path, directories_list.copy())))
148
+ patterns: list[str] = patterns_list.copy()
149
+
150
+ current_working_directory = Path.cwd()
151
+ for pattern in patterns_list:
152
+ # Special case for the .* pattern, otherwise this would only match
153
+ # hidden directories which is probably undesired
154
+ if pattern == ".*":
155
+ continue # pragma: py-not-linux
156
+ patterns.append(pattern)
157
+ if is_dir(Path(pattern)):
158
+ directories.append(Path(pattern))
159
+ else:
160
+ for match in current_working_directory.glob(pattern):
161
+ if is_dir(match):
162
+ directories.append(match)
163
+
164
+ directories = list(set(directories))
165
+ directories = list(map(Path, directories))
166
+ directories = list(map(lambda x: x.resolve(), directories))
167
+ directories = list({reload_path for reload_path in directories if is_dir(reload_path)})
168
+
169
+ children = []
170
+ for j in range(len(directories)):
171
+ for k in range(j + 1, len(directories)): # pragma: full coverage
172
+ if directories[j] in directories[k].parents:
173
+ children.append(directories[k])
174
+ elif directories[k] in directories[j].parents:
175
+ children.append(directories[j])
176
+
177
+ directories = list(set(directories).difference(set(children)))
178
+
179
+ return list(set(patterns)), directories
180
+
181
+
182
+ def _normalize_dirs(dirs: list[str] | str | None) -> list[str]:
183
+ if dirs is None:
184
+ return []
185
+ if isinstance(dirs, str):
186
+ return [dirs]
187
+ return list(set(dirs))
188
+
189
+
190
+ class Config:
191
+ def __init__(
192
+ self,
193
+ app: ASGIApplication | Callable[..., Any] | str,
194
+ host: str = "127.0.0.1",
195
+ port: int = 8000,
196
+ uds: str | None = None,
197
+ fd: int | None = None,
198
+ loop: LoopFactoryType | str = "auto",
199
+ http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto",
200
+ ws: type[asyncio.Protocol] | WSProtocolType | str = "auto",
201
+ ws_max_size: int = 16 * 1024 * 1024,
202
+ ws_max_queue: int = 32,
203
+ ws_ping_interval: float | None = 20.0,
204
+ ws_ping_timeout: float | None = 20.0,
205
+ ws_per_message_deflate: bool = True,
206
+ lifespan: LifespanType = "auto",
207
+ env_file: str | os.PathLike[str] | None = None,
208
+ log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG,
209
+ log_level: str | int | None = None,
210
+ access_log: bool = True,
211
+ use_colors: bool | None = None,
212
+ interface: InterfaceType = "auto",
213
+ reload: bool = False,
214
+ reload_dirs: list[str] | str | None = None,
215
+ reload_delay: float = 0.25,
216
+ reload_includes: list[str] | str | None = None,
217
+ reload_excludes: list[str] | str | None = None,
218
+ workers: int | None = None,
219
+ proxy_headers: bool = True,
220
+ server_header: bool = True,
221
+ date_header: bool = True,
222
+ forwarded_allow_ips: list[str] | str | None = None,
223
+ root_path: str = "",
224
+ limit_concurrency: int | None = None,
225
+ limit_max_requests: int | None = None,
226
+ limit_max_requests_jitter: int = 0,
227
+ backlog: int = 2048,
228
+ timeout_keep_alive: int = 5,
229
+ timeout_notify: int = 30,
230
+ timeout_graceful_shutdown: int | None = None,
231
+ timeout_worker_healthcheck: int = 5,
232
+ callback_notify: Callable[..., Awaitable[None]] | None = None,
233
+ ssl_keyfile: str | os.PathLike[str] | None = None,
234
+ ssl_certfile: str | os.PathLike[str] | None = None,
235
+ ssl_keyfile_password: str | None = None,
236
+ ssl_version: int = SSL_PROTOCOL_VERSION,
237
+ ssl_cert_reqs: int = ssl.CERT_NONE,
238
+ ssl_ca_certs: str | os.PathLike[str] | None = None,
239
+ ssl_ciphers: str | None = None,
240
+ ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None,
241
+ headers: list[tuple[str, str]] | None = None,
242
+ factory: bool = False,
243
+ h11_max_incomplete_event_size: int | None = None,
244
+ reset_contextvars: bool = False,
245
+ ):
246
+ self.app = app
247
+ self.host = host
248
+ self.port = port
249
+ self.uds = uds
250
+ self.fd = fd
251
+ self.loop = loop
252
+ self.http = http
253
+ self.ws = ws
254
+ self.ws_max_size = ws_max_size
255
+ self.ws_max_queue = ws_max_queue
256
+ self.ws_ping_interval = ws_ping_interval
257
+ self.ws_ping_timeout = ws_ping_timeout
258
+ self.ws_per_message_deflate = ws_per_message_deflate
259
+ self.lifespan = lifespan
260
+ self.log_config = log_config
261
+ self.log_level = log_level
262
+ self.access_log = access_log
263
+ self.use_colors = use_colors
264
+ self.interface = interface
265
+ self.reload = reload
266
+ self.reload_delay = reload_delay
267
+ self.workers = workers or 1
268
+ self.proxy_headers = proxy_headers
269
+ self.server_header = server_header
270
+ self.date_header = date_header
271
+ self.root_path = root_path
272
+ self.limit_concurrency = limit_concurrency
273
+ self.limit_max_requests = limit_max_requests
274
+ self.limit_max_requests_jitter = limit_max_requests_jitter
275
+ self.backlog = backlog
276
+ self.timeout_keep_alive = timeout_keep_alive
277
+ self.timeout_notify = timeout_notify
278
+ self.timeout_graceful_shutdown = timeout_graceful_shutdown
279
+ self.timeout_worker_healthcheck = timeout_worker_healthcheck
280
+ self.callback_notify = callback_notify
281
+ self.ssl_keyfile = ssl_keyfile
282
+ self.ssl_certfile = ssl_certfile
283
+ self.ssl_keyfile_password = ssl_keyfile_password
284
+ self.ssl_version = ssl_version
285
+ self.ssl_cert_reqs = ssl_cert_reqs
286
+ self.ssl_ca_certs = ssl_ca_certs
287
+ self.ssl_ciphers = ssl_ciphers
288
+ self.ssl_context_factory = ssl_context_factory
289
+ self.headers: list[tuple[str, str]] = headers or []
290
+ self.encoded_headers: list[tuple[bytes, bytes]] = []
291
+ self.factory = factory
292
+ self.h11_max_incomplete_event_size = h11_max_incomplete_event_size
293
+ self.reset_contextvars = reset_contextvars
294
+
295
+ self.loaded = False
296
+ self.configure_logging()
297
+
298
+ self.reload_dirs: list[Path] = []
299
+ self.reload_dirs_excludes: list[Path] = []
300
+ self.reload_includes: list[str] = []
301
+ self.reload_excludes: list[str] = []
302
+
303
+ if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload:
304
+ logger.warning(
305
+ "Current configuration will not reload as not all conditions are met, please refer to documentation."
306
+ )
307
+
308
+ if self.should_reload:
309
+ reload_dirs = _normalize_dirs(reload_dirs)
310
+ reload_includes = _normalize_dirs(reload_includes)
311
+ reload_excludes = _normalize_dirs(reload_excludes)
312
+
313
+ self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs)
314
+
315
+ self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, [])
316
+
317
+ reload_dirs_tmp = self.reload_dirs.copy()
318
+
319
+ for directory in self.reload_dirs_excludes:
320
+ for reload_directory in reload_dirs_tmp:
321
+ if directory == reload_directory or directory in reload_directory.parents:
322
+ try:
323
+ self.reload_dirs.remove(reload_directory)
324
+ except ValueError: # pragma: full coverage
325
+ pass
326
+
327
+ for pattern in self.reload_excludes:
328
+ if pattern in self.reload_includes:
329
+ self.reload_includes.remove(pattern) # pragma: full coverage
330
+
331
+ if not self.reload_dirs:
332
+ if reload_dirs:
333
+ logger.warning(
334
+ "Provided reload directories %s did not contain valid "
335
+ + "directories, watching current working directory.",
336
+ reload_dirs,
337
+ )
338
+ self.reload_dirs = [Path.cwd()]
339
+
340
+ logger.info(
341
+ "Will watch for changes in these directories: %s",
342
+ sorted(list(map(str, self.reload_dirs))),
343
+ )
344
+
345
+ if env_file is not None:
346
+ from dotenv import load_dotenv
347
+
348
+ logger.info("Loading environment from '%s'", env_file)
349
+ load_dotenv(dotenv_path=env_file)
350
+
351
+ if workers is None and "WEB_CONCURRENCY" in os.environ:
352
+ self.workers = int(os.environ["WEB_CONCURRENCY"])
353
+
354
+ self.forwarded_allow_ips: list[str] | str
355
+ if forwarded_allow_ips is None:
356
+ self.forwarded_allow_ips = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1")
357
+ else:
358
+ self.forwarded_allow_ips = forwarded_allow_ips # pragma: full coverage
359
+
360
+ if self.reload and self.workers > 1:
361
+ logger.warning('"workers" flag is ignored when reloading is enabled.')
362
+
363
+ @property
364
+ def asgi_version(self) -> Literal["2.0", "3.0"]:
365
+ mapping: dict[str, Literal["2.0", "3.0"]] = {
366
+ "asgi2": "2.0",
367
+ "asgi3": "3.0",
368
+ "wsgi": "3.0",
369
+ }
370
+ return mapping[self.interface]
371
+
372
+ @property
373
+ def is_ssl(self) -> bool:
374
+ return bool(self.ssl_keyfile or self.ssl_certfile or self.ssl_context_factory)
375
+
376
+ @property
377
+ def use_subprocess(self) -> bool:
378
+ return bool(self.reload or self.workers > 1)
379
+
380
+ def configure_logging(self) -> None:
381
+ logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")
382
+
383
+ if self.log_config is not None:
384
+ if isinstance(self.log_config, os.PathLike):
385
+ self.log_config = os.fspath(self.log_config)
386
+
387
+ if isinstance(self.log_config, dict):
388
+ if self.use_colors in (True, False):
389
+ self.log_config["formatters"]["default"]["use_colors"] = self.use_colors
390
+ self.log_config["formatters"]["access"]["use_colors"] = self.use_colors
391
+ logging.config.dictConfig(self.log_config)
392
+ elif isinstance(self.log_config, str) and self.log_config.endswith(".json"):
393
+ with open(self.log_config) as file:
394
+ loaded_config = json.load(file)
395
+ logging.config.dictConfig(loaded_config)
396
+ elif isinstance(self.log_config, str) and self.log_config.endswith((".yaml", ".yml")):
397
+ try:
398
+ import yaml
399
+ except ImportError as e:
400
+ raise ImportError(
401
+ "Install the PyYAML package or uvicorn[standard] to use `--log-config` with YAML files."
402
+ ) from e
403
+
404
+ with open(self.log_config) as file:
405
+ loaded_config = yaml.safe_load(file)
406
+ logging.config.dictConfig(loaded_config)
407
+ else:
408
+ # See the note about fileConfig() here:
409
+ # https://docs.python.org/3/library/logging.config.html#configuration-file-format
410
+ logging.config.fileConfig(self.log_config, disable_existing_loggers=False)
411
+
412
+ if self.log_level is not None:
413
+ if isinstance(self.log_level, str):
414
+ log_level = LOG_LEVELS[self.log_level.lower()]
415
+ else:
416
+ log_level = self.log_level
417
+ logging.getLogger("uvicorn.error").setLevel(log_level)
418
+ logging.getLogger("uvicorn.access").setLevel(log_level)
419
+ logging.getLogger("uvicorn.asgi").setLevel(log_level)
420
+ if self.access_log is False:
421
+ logging.getLogger("uvicorn.access").handlers = []
422
+ logging.getLogger("uvicorn.access").propagate = False
423
+
424
+ def load_app(self) -> Any:
425
+ """Import the app and return it. Exits on failure."""
426
+ try:
427
+ return import_from_string(self.app)
428
+ except ImportFromStringError as exc:
429
+ logger.error("Error loading ASGI app. %s" % exc)
430
+ sys.exit(STARTUP_FAILURE)
431
+
432
+ def load(self) -> None:
433
+ assert not self.loaded
434
+
435
+ if self.ssl_context_factory is not None:
436
+
437
+ def default_factory() -> ssl.SSLContext:
438
+ if not self.ssl_certfile:
439
+ raise RuntimeError(
440
+ "`default_ssl_context_factory()` requires `ssl_certfile` to be set on `Config`. "
441
+ "Either pass `ssl_certfile` (and optionally `ssl_keyfile`) or build the `SSLContext` "
442
+ "directly inside `ssl_context_factory` without calling the default factory."
443
+ )
444
+ return create_ssl_context(
445
+ keyfile=self.ssl_keyfile,
446
+ certfile=self.ssl_certfile,
447
+ password=self.ssl_keyfile_password,
448
+ ssl_version=self.ssl_version,
449
+ cert_reqs=self.ssl_cert_reqs,
450
+ ca_certs=self.ssl_ca_certs,
451
+ ciphers=self.ssl_ciphers,
452
+ )
453
+
454
+ context = self.ssl_context_factory(self, default_factory)
455
+ if not isinstance(context, ssl.SSLContext):
456
+ raise TypeError(f"`ssl_context_factory` must return an `ssl.SSLContext`, got {type(context).__name__}")
457
+ self.ssl: ssl.SSLContext | None = context
458
+ elif self.is_ssl:
459
+ assert self.ssl_certfile
460
+ self.ssl = create_ssl_context(
461
+ keyfile=self.ssl_keyfile,
462
+ certfile=self.ssl_certfile,
463
+ password=self.ssl_keyfile_password,
464
+ ssl_version=self.ssl_version,
465
+ cert_reqs=self.ssl_cert_reqs,
466
+ ca_certs=self.ssl_ca_certs,
467
+ ciphers=self.ssl_ciphers,
468
+ )
469
+ else:
470
+ self.ssl = None
471
+
472
+ encoded_headers = [(key.lower().encode("latin1"), value.encode("latin1")) for key, value in self.headers]
473
+ self.encoded_headers = (
474
+ [(b"server", b"uvicorn")] + encoded_headers
475
+ if b"server" not in dict(encoded_headers) and self.server_header
476
+ else encoded_headers
477
+ )
478
+
479
+ if isinstance(self.http, str):
480
+ http_protocol_class = import_from_string(HTTP_PROTOCOLS.get(self.http, self.http))
481
+ self.http_protocol_class: type[asyncio.Protocol] = http_protocol_class
482
+ else:
483
+ self.http_protocol_class = self.http
484
+
485
+ if isinstance(self.ws, str):
486
+ ws_protocol_class = import_from_string(WS_PROTOCOLS.get(self.ws, self.ws))
487
+ self.ws_protocol_class: type[asyncio.Protocol] | None = ws_protocol_class
488
+ else:
489
+ self.ws_protocol_class = self.ws
490
+
491
+ self.lifespan_class = import_from_string(LIFESPAN[self.lifespan])
492
+
493
+ self.loaded_app = self.load_app()
494
+
495
+ try:
496
+ self.loaded_app = self.loaded_app()
497
+ except TypeError as exc:
498
+ if self.factory:
499
+ logger.error("Error loading ASGI app factory: %s", exc)
500
+ sys.exit(STARTUP_FAILURE)
501
+ else:
502
+ if not self.factory:
503
+ logger.warning(
504
+ "ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly."
505
+ )
506
+
507
+ if self.interface == "auto":
508
+ if inspect.isclass(self.loaded_app):
509
+ use_asgi_3 = hasattr(self.loaded_app, "__await__")
510
+ elif inspect.isfunction(self.loaded_app):
511
+ use_asgi_3 = iscoroutinefunction(self.loaded_app)
512
+ else:
513
+ call = getattr(self.loaded_app, "__call__", None)
514
+ use_asgi_3 = iscoroutinefunction(call)
515
+ self.interface = "asgi3" if use_asgi_3 else "asgi2"
516
+
517
+ if self.interface == "wsgi":
518
+ self.loaded_app = WSGIMiddleware(self.loaded_app)
519
+ self.ws_protocol_class = None
520
+ elif self.interface == "asgi2":
521
+ self.loaded_app = ASGI2Middleware(self.loaded_app)
522
+
523
+ if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL:
524
+ self.loaded_app = MessageLoggerMiddleware(self.loaded_app)
525
+ if self.proxy_headers:
526
+ self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips)
527
+
528
+ self.loaded = True
529
+
530
+ def setup_event_loop(self) -> None:
531
+ raise AttributeError(
532
+ "The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\n"
533
+ "None of those methods are supposed to be used directly. If you are doing it, please let me know here: "
534
+ "https://github.com/Kludex/uvicorn/discussions/2706. Thank you, and sorry for the inconvenience."
535
+ )
536
+
537
+ def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] | None:
538
+ if self.loop in LOOP_FACTORIES:
539
+ loop_factory: Callable[..., Any] | None = import_from_string(LOOP_FACTORIES[self.loop])
540
+ else:
541
+ try:
542
+ return import_from_string(self.loop)
543
+ except ImportFromStringError as exc:
544
+ logger.error("Error loading custom loop setup function. %s" % exc)
545
+ sys.exit(STARTUP_FAILURE)
546
+ if loop_factory is None:
547
+ return None
548
+ return loop_factory(use_subprocess=self.use_subprocess)
549
+
550
+ def bind_socket(self) -> socket.socket:
551
+ logger_args: list[str | int]
552
+ if self.uds is not None: # pragma: py-win32
553
+ path = self.uds
554
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
555
+ try:
556
+ sock.bind(path)
557
+ uds_perms = 0o666
558
+ os.chmod(self.uds, uds_perms)
559
+ except OSError as exc: # pragma: full coverage
560
+ logger.error(exc)
561
+ sys.exit(STARTUP_FAILURE)
562
+
563
+ message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)"
564
+ sock_name_format = "%s"
565
+ color_message = "Uvicorn running on " + style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
566
+ logger_args = [self.uds]
567
+ elif self.fd is not None: # pragma: py-win32
568
+ sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
569
+ message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
570
+ fd_name_format = "%s"
571
+ color_message = "Uvicorn running on " + style(fd_name_format, bold=True) + " (Press CTRL+C to quit)"
572
+ logger_args = [sock.getsockname()]
573
+ else:
574
+ family = socket.AF_INET
575
+ addr_format = "%s://%s:%d"
576
+
577
+ if self.host and ":" in self.host: # pragma: full coverage
578
+ # It's an IPv6 address.
579
+ family = socket.AF_INET6
580
+ addr_format = "%s://[%s]:%d"
581
+
582
+ sock = socket.socket(family=family)
583
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
584
+ try:
585
+ sock.bind((self.host, self.port))
586
+ except OSError as exc: # pragma: full coverage
587
+ logger.error(exc)
588
+ sys.exit(STARTUP_FAILURE)
589
+
590
+ message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
591
+ color_message = "Uvicorn running on " + style(addr_format, bold=True) + " (Press CTRL+C to quit)"
592
+ protocol_name = "https" if self.is_ssl else "http"
593
+ logger_args = [protocol_name, self.host, sock.getsockname()[1]]
594
+ logger.info(message, *logger_args, extra={"color_message": color_message})
595
+ sock.set_inheritable(True)
596
+ return sock
597
+
598
+ @property
599
+ def should_reload(self) -> bool:
600
+ return isinstance(self.app, str) and self.reload
.venv/Lib/site-packages/uvicorn/importer.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib
2
+ from typing import Any
3
+
4
+
5
+ class ImportFromStringError(Exception):
6
+ pass
7
+
8
+
9
+ def import_from_string(import_str: Any) -> Any:
10
+ if not isinstance(import_str, str):
11
+ return import_str
12
+
13
+ module_str, _, attrs_str = import_str.partition(":")
14
+ if not module_str or not attrs_str:
15
+ message = 'Import string "{import_str}" must be in format "<module>:<attribute>".'
16
+ raise ImportFromStringError(message.format(import_str=import_str))
17
+
18
+ try:
19
+ module = importlib.import_module(module_str)
20
+ except ModuleNotFoundError as exc:
21
+ if exc.name != module_str:
22
+ raise exc from None
23
+ message = 'Could not import module "{module_str}".'
24
+ raise ImportFromStringError(message.format(module_str=module_str))
25
+
26
+ instance = module
27
+ try:
28
+ for attr_str in attrs_str.split("."):
29
+ instance = getattr(instance, attr_str)
30
+ except AttributeError:
31
+ message = 'Attribute "{attrs_str}" not found in module "{module_str}".'
32
+ raise ImportFromStringError(message.format(attrs_str=attrs_str, module_str=module_str))
33
+
34
+ return instance
.venv/Lib/site-packages/uvicorn/lifespan/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/lifespan/off.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from uvicorn import Config
6
+
7
+
8
+ class LifespanOff:
9
+ def __init__(self, config: Config) -> None:
10
+ self.should_exit = False
11
+ self.state: dict[str, Any] = {}
12
+
13
+ async def startup(self) -> None:
14
+ pass
15
+
16
+ async def shutdown(self) -> None:
17
+ pass
.venv/Lib/site-packages/uvicorn/lifespan/on.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from asyncio import Queue
6
+ from typing import Any
7
+
8
+ from uvicorn import Config
9
+ from uvicorn._types import (
10
+ LifespanScope,
11
+ LifespanShutdownCompleteEvent,
12
+ LifespanShutdownEvent,
13
+ LifespanShutdownFailedEvent,
14
+ LifespanStartupCompleteEvent,
15
+ LifespanStartupEvent,
16
+ LifespanStartupFailedEvent,
17
+ )
18
+
19
+ LifespanReceiveMessage = LifespanStartupEvent | LifespanShutdownEvent
20
+ LifespanSendMessage = (
21
+ LifespanStartupFailedEvent
22
+ | LifespanShutdownFailedEvent
23
+ | LifespanStartupCompleteEvent
24
+ | LifespanShutdownCompleteEvent
25
+ )
26
+
27
+
28
+ STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol."
29
+
30
+
31
+ class LifespanOn:
32
+ def __init__(self, config: Config) -> None:
33
+ if not config.loaded:
34
+ config.load()
35
+
36
+ self.config = config
37
+ self.logger = logging.getLogger("uvicorn.error")
38
+ self.startup_event = asyncio.Event()
39
+ self.shutdown_event = asyncio.Event()
40
+ self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue()
41
+ self.error_occurred = False
42
+ self.startup_failed = False
43
+ self.shutdown_failed = False
44
+ self.should_exit = False
45
+ self.state: dict[str, Any] = {}
46
+
47
+ async def startup(self) -> None:
48
+ self.logger.info("Waiting for application startup.")
49
+
50
+ loop = asyncio.get_event_loop()
51
+ main_lifespan_task = loop.create_task(self.main()) # noqa: F841
52
+ # Keep a hard reference to prevent garbage collection
53
+ # See https://github.com/Kludex/uvicorn/pull/972
54
+ startup_event: LifespanStartupEvent = {"type": "lifespan.startup"}
55
+ await self.receive_queue.put(startup_event)
56
+ await self.startup_event.wait()
57
+
58
+ if self.startup_failed or (self.error_occurred and self.config.lifespan == "on"):
59
+ self.logger.error("Application startup failed. Exiting.")
60
+ self.should_exit = True
61
+ else:
62
+ self.logger.info("Application startup complete.")
63
+
64
+ async def shutdown(self) -> None:
65
+ if self.error_occurred:
66
+ return
67
+ self.logger.info("Waiting for application shutdown.")
68
+ shutdown_event: LifespanShutdownEvent = {"type": "lifespan.shutdown"}
69
+ await self.receive_queue.put(shutdown_event)
70
+ await self.shutdown_event.wait()
71
+
72
+ if self.shutdown_failed or (self.error_occurred and self.config.lifespan == "on"):
73
+ self.logger.error("Application shutdown failed. Exiting.")
74
+ self.should_exit = True
75
+ else:
76
+ self.logger.info("Application shutdown complete.")
77
+
78
+ async def main(self) -> None:
79
+ try:
80
+ app = self.config.loaded_app
81
+ scope: LifespanScope = {
82
+ "type": "lifespan",
83
+ "asgi": {"version": self.config.asgi_version, "spec_version": "2.0"},
84
+ "state": self.state,
85
+ }
86
+ await app(scope, self.receive, self.send)
87
+ except BaseException as exc:
88
+ self.asgi = None
89
+ self.error_occurred = True
90
+ if self.startup_failed or self.shutdown_failed:
91
+ return
92
+ if self.config.lifespan == "auto":
93
+ msg = "ASGI 'lifespan' protocol appears unsupported."
94
+ self.logger.info(msg)
95
+ else:
96
+ msg = "Exception in 'lifespan' protocol\n"
97
+ self.logger.error(msg, exc_info=exc)
98
+ finally:
99
+ self.startup_event.set()
100
+ self.shutdown_event.set()
101
+
102
+ async def send(self, message: LifespanSendMessage) -> None:
103
+ assert message["type"] in (
104
+ "lifespan.startup.complete",
105
+ "lifespan.startup.failed",
106
+ "lifespan.shutdown.complete",
107
+ "lifespan.shutdown.failed",
108
+ )
109
+
110
+ if message["type"] == "lifespan.startup.complete":
111
+ assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
112
+ assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
113
+ self.startup_event.set()
114
+
115
+ elif message["type"] == "lifespan.startup.failed":
116
+ assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
117
+ assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
118
+ self.startup_event.set()
119
+ self.startup_failed = True
120
+ if message.get("message"):
121
+ self.logger.error(message["message"])
122
+
123
+ elif message["type"] == "lifespan.shutdown.complete":
124
+ assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
125
+ assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
126
+ self.shutdown_event.set()
127
+
128
+ elif message["type"] == "lifespan.shutdown.failed":
129
+ assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
130
+ assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
131
+ self.shutdown_event.set()
132
+ self.shutdown_failed = True
133
+ if message.get("message"):
134
+ self.logger.error(message["message"])
135
+
136
+ async def receive(self) -> LifespanReceiveMessage:
137
+ return await self.receive_queue.get()
.venv/Lib/site-packages/uvicorn/logging.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import http
4
+ import logging
5
+ import sys
6
+ from copy import copy
7
+ from typing import Literal
8
+
9
+ from uvicorn._ansi import style as ansi_style
10
+
11
+ TRACE_LOG_LEVEL = 5
12
+
13
+
14
+ class ColourizedFormatter(logging.Formatter):
15
+ """
16
+ A custom log formatter class that:
17
+
18
+ * Outputs the LOG_LEVEL with an appropriate color.
19
+ * If a log call includes an `extra={"color_message": ...}` it will be used
20
+ for formatting the output, instead of the plain text message.
21
+ """
22
+
23
+ level_name_colors = {
24
+ TRACE_LOG_LEVEL: lambda level_name: ansi_style(str(level_name), fg="blue"),
25
+ logging.DEBUG: lambda level_name: ansi_style(str(level_name), fg="cyan"),
26
+ logging.INFO: lambda level_name: ansi_style(str(level_name), fg="green"),
27
+ logging.WARNING: lambda level_name: ansi_style(str(level_name), fg="yellow"),
28
+ logging.ERROR: lambda level_name: ansi_style(str(level_name), fg="red"),
29
+ logging.CRITICAL: lambda level_name: ansi_style(str(level_name), fg="bright_red"),
30
+ }
31
+
32
+ def __init__(
33
+ self,
34
+ fmt: str | None = None,
35
+ datefmt: str | None = None,
36
+ style: Literal["%", "{", "$"] = "%",
37
+ use_colors: bool | None = None,
38
+ ):
39
+ if use_colors in (True, False):
40
+ self.use_colors = use_colors
41
+ else:
42
+ self.use_colors = sys.stdout.isatty()
43
+ super().__init__(fmt=fmt, datefmt=datefmt, style=style)
44
+
45
+ def color_level_name(self, level_name: str, level_no: int) -> str:
46
+ def default(level_name: str) -> str:
47
+ return str(level_name) # pragma: no cover
48
+
49
+ func = self.level_name_colors.get(level_no, default)
50
+ return func(level_name)
51
+
52
+ def should_use_colors(self) -> bool:
53
+ return True # pragma: no cover
54
+
55
+ def formatMessage(self, record: logging.LogRecord) -> str:
56
+ recordcopy = copy(record)
57
+ levelname = recordcopy.levelname
58
+ separator = " " * (8 - len(recordcopy.levelname))
59
+ if self.use_colors:
60
+ levelname = self.color_level_name(levelname, recordcopy.levelno)
61
+ if "color_message" in recordcopy.__dict__:
62
+ recordcopy.msg = recordcopy.__dict__["color_message"]
63
+ recordcopy.__dict__["message"] = recordcopy.getMessage()
64
+ recordcopy.__dict__["levelprefix"] = levelname + ":" + separator
65
+ return super().formatMessage(recordcopy)
66
+
67
+
68
+ class DefaultFormatter(ColourizedFormatter):
69
+ def should_use_colors(self) -> bool:
70
+ return sys.stderr.isatty() # pragma: no cover
71
+
72
+
73
+ class AccessFormatter(ColourizedFormatter):
74
+ status_code_colours = {
75
+ 1: lambda code: ansi_style(str(code), fg="bright_white"),
76
+ 2: lambda code: ansi_style(str(code), fg="green"),
77
+ 3: lambda code: ansi_style(str(code), fg="yellow"),
78
+ 4: lambda code: ansi_style(str(code), fg="red"),
79
+ 5: lambda code: ansi_style(str(code), fg="bright_red"),
80
+ }
81
+
82
+ def get_status_code(self, status_code: int) -> str:
83
+ try:
84
+ status_phrase = http.HTTPStatus(status_code).phrase
85
+ except ValueError:
86
+ status_phrase = ""
87
+ status_and_phrase = f"{status_code} {status_phrase}"
88
+ if self.use_colors:
89
+
90
+ def default(code: int) -> str:
91
+ return status_and_phrase # pragma: no cover
92
+
93
+ func = self.status_code_colours.get(status_code // 100, default)
94
+ return func(status_and_phrase)
95
+ return status_and_phrase
96
+
97
+ def formatMessage(self, record: logging.LogRecord) -> str:
98
+ recordcopy = copy(record)
99
+ (
100
+ client_addr,
101
+ method,
102
+ full_path,
103
+ http_version,
104
+ status_code,
105
+ ) = recordcopy.args # type: ignore[misc]
106
+ status_code = self.get_status_code(int(status_code)) # type: ignore[arg-type]
107
+ request_line = f"{method} {full_path} HTTP/{http_version}"
108
+ if self.use_colors:
109
+ request_line = ansi_style(request_line, bold=True)
110
+ recordcopy.__dict__.update(
111
+ {
112
+ "client_addr": client_addr,
113
+ "request_line": request_line,
114
+ "status_code": status_code,
115
+ }
116
+ )
117
+ return super().formatMessage(recordcopy)
.venv/Lib/site-packages/uvicorn/loops/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/loops/asyncio.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import sys
5
+ from collections.abc import Callable
6
+
7
+
8
+ def asyncio_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:
9
+ if sys.platform == "win32" and not use_subprocess:
10
+ return asyncio.ProactorEventLoop
11
+ return asyncio.SelectorEventLoop
.venv/Lib/site-packages/uvicorn/loops/auto.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Callable
5
+
6
+
7
+ def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:
8
+ try:
9
+ import uvloop # noqa
10
+ except ImportError: # pragma: no cover
11
+ from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory
12
+
13
+ return loop_factory(use_subprocess=use_subprocess)
14
+ else: # pragma: no cover
15
+ from uvicorn.loops.uvloop import uvloop_loop_factory
16
+
17
+ return uvloop_loop_factory(use_subprocess=use_subprocess)
.venv/Lib/site-packages/uvicorn/loops/uvloop.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Callable
5
+
6
+ import uvloop
7
+
8
+
9
+ def uvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]:
10
+ return uvloop.new_event_loop
.venv/Lib/site-packages/uvicorn/main.py ADDED
@@ -0,0 +1,645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import platform
7
+ import ssl
8
+ import sys
9
+ import warnings
10
+ from collections.abc import Callable
11
+ from configparser import RawConfigParser
12
+ from typing import IO, Any, get_args
13
+
14
+ import click
15
+
16
+ import uvicorn
17
+ from uvicorn._types import ASGIApplication
18
+ from uvicorn.config import (
19
+ INTERFACES,
20
+ LIFESPAN,
21
+ LOG_LEVELS,
22
+ LOGGING_CONFIG,
23
+ SSL_PROTOCOL_VERSION,
24
+ STARTUP_FAILURE,
25
+ Config,
26
+ HTTPProtocolType,
27
+ InterfaceType,
28
+ LifespanType,
29
+ LoopFactoryType,
30
+ WSProtocolType,
31
+ )
32
+ from uvicorn.server import Server
33
+ from uvicorn.supervisors import ChangeReload, Multiprocess
34
+
35
+ LEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys()))
36
+ LIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys()))
37
+ INTERFACE_CHOICES = click.Choice(INTERFACES)
38
+
39
+
40
+ def _metavar_from_type(_type: Any) -> str:
41
+ return f"[{'|'.join(key for key in get_args(_type) if key != 'none')}]"
42
+
43
+
44
+ logger = logging.getLogger("uvicorn.error")
45
+
46
+
47
+ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None:
48
+ if not value or ctx.resilient_parsing:
49
+ return
50
+ click.echo(
51
+ "Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( # noqa: UP032
52
+ version=uvicorn.__version__,
53
+ py_implementation=platform.python_implementation(),
54
+ py_version=platform.python_version(),
55
+ system=platform.system(),
56
+ )
57
+ )
58
+ ctx.exit()
59
+
60
+
61
+ @click.command(context_settings={"auto_envvar_prefix": "UVICORN"})
62
+ @click.argument("app", envvar="UVICORN_APP")
63
+ @click.option(
64
+ "--host",
65
+ type=str,
66
+ default="127.0.0.1",
67
+ help="Bind socket to this host.",
68
+ show_default=True,
69
+ )
70
+ @click.option(
71
+ "--port",
72
+ type=int,
73
+ default=8000,
74
+ help="Bind socket to this port. If 0, an available port will be picked.",
75
+ show_default=True,
76
+ )
77
+ @click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.")
78
+ @click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.")
79
+ @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.")
80
+ @click.option(
81
+ "--reload-dir",
82
+ "reload_dirs",
83
+ multiple=True,
84
+ help="Set reload directories explicitly, instead of using the current working directory.",
85
+ type=click.Path(exists=True),
86
+ )
87
+ @click.option(
88
+ "--reload-include",
89
+ "reload_includes",
90
+ multiple=True,
91
+ help="Set glob patterns to include while watching for files. Includes '*.py' "
92
+ "by default; these defaults can be overridden with `--reload-exclude`. "
93
+ "This option has no effect unless watchfiles is installed.",
94
+ )
95
+ @click.option(
96
+ "--reload-exclude",
97
+ "reload_excludes",
98
+ multiple=True,
99
+ help="Set glob patterns to exclude while watching for files. Includes "
100
+ "'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden "
101
+ "with `--reload-include`. This option has no effect unless watchfiles is "
102
+ "installed.",
103
+ )
104
+ @click.option(
105
+ "--reload-delay",
106
+ type=float,
107
+ default=0.25,
108
+ show_default=True,
109
+ help="Delay between previous and next check if application needs to be. Defaults to 0.25s.",
110
+ )
111
+ @click.option(
112
+ "--workers",
113
+ default=None,
114
+ type=int,
115
+ help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment"
116
+ " variable if available, or 1. Not valid with --reload.",
117
+ )
118
+ @click.option(
119
+ "--loop",
120
+ type=str,
121
+ metavar=_metavar_from_type(LoopFactoryType),
122
+ default="auto",
123
+ help="Event loop factory implementation.",
124
+ show_default=True,
125
+ )
126
+ @click.option(
127
+ "--http",
128
+ type=str,
129
+ metavar=_metavar_from_type(HTTPProtocolType),
130
+ default="auto",
131
+ help="HTTP protocol implementation.",
132
+ show_default=True,
133
+ )
134
+ @click.option(
135
+ "--ws",
136
+ type=str,
137
+ metavar=_metavar_from_type(WSProtocolType),
138
+ default="auto",
139
+ help="WebSocket protocol implementation.",
140
+ show_default=True,
141
+ )
142
+ @click.option(
143
+ "--ws-max-size",
144
+ type=int,
145
+ default=16777216,
146
+ help="WebSocket max size message in bytes",
147
+ show_default=True,
148
+ )
149
+ @click.option(
150
+ "--ws-max-queue",
151
+ type=int,
152
+ default=32,
153
+ help="The maximum length of the WebSocket message queue.",
154
+ show_default=True,
155
+ )
156
+ @click.option(
157
+ "--ws-ping-interval",
158
+ type=float,
159
+ default=20.0,
160
+ help="WebSocket ping interval in seconds.",
161
+ show_default=True,
162
+ )
163
+ @click.option(
164
+ "--ws-ping-timeout",
165
+ type=float,
166
+ default=20.0,
167
+ help="WebSocket ping timeout in seconds.",
168
+ show_default=True,
169
+ )
170
+ @click.option(
171
+ "--ws-per-message-deflate",
172
+ type=bool,
173
+ default=True,
174
+ help="WebSocket per-message-deflate compression",
175
+ show_default=True,
176
+ )
177
+ @click.option(
178
+ "--lifespan",
179
+ type=LIFESPAN_CHOICES,
180
+ default="auto",
181
+ help="Lifespan implementation.",
182
+ show_default=True,
183
+ )
184
+ @click.option(
185
+ "--interface",
186
+ type=INTERFACE_CHOICES,
187
+ default="auto",
188
+ help="Select ASGI3, ASGI2, or WSGI as the application interface.",
189
+ show_default=True,
190
+ )
191
+ @click.option(
192
+ "--env-file",
193
+ type=click.Path(exists=True),
194
+ default=None,
195
+ help="Environment configuration file.",
196
+ show_default=True,
197
+ )
198
+ @click.option(
199
+ "--log-config",
200
+ type=click.Path(exists=True),
201
+ default=None,
202
+ help="Logging configuration file. Supported formats: .ini, .json, .yaml.",
203
+ show_default=True,
204
+ )
205
+ @click.option(
206
+ "--log-level",
207
+ type=LEVEL_CHOICES,
208
+ default=None,
209
+ help="Log level. [default: info]",
210
+ show_default=True,
211
+ )
212
+ @click.option(
213
+ "--access-log/--no-access-log",
214
+ is_flag=True,
215
+ default=True,
216
+ help="Enable/Disable access log.",
217
+ )
218
+ @click.option(
219
+ "--use-colors/--no-use-colors",
220
+ is_flag=True,
221
+ default=None,
222
+ help="Enable/Disable colorized logging.",
223
+ )
224
+ @click.option(
225
+ "--proxy-headers/--no-proxy-headers",
226
+ is_flag=True,
227
+ default=True,
228
+ help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate url scheme and remote address info.",
229
+ )
230
+ @click.option(
231
+ "--server-header/--no-server-header",
232
+ is_flag=True,
233
+ default=True,
234
+ help="Enable/Disable default Server header.",
235
+ )
236
+ @click.option(
237
+ "--date-header/--no-date-header",
238
+ is_flag=True,
239
+ default=True,
240
+ help="Enable/Disable default Date header.",
241
+ )
242
+ @click.option(
243
+ "--forwarded-allow-ips",
244
+ type=str,
245
+ default=None,
246
+ help="Comma separated list of IP Addresses, IP Networks, or literals "
247
+ "(e.g. UNIX Socket path) to trust with proxy headers. Defaults to the "
248
+ "$FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. "
249
+ "The literal '*' means trust everything.",
250
+ )
251
+ @click.option(
252
+ "--root-path",
253
+ type=str,
254
+ default="",
255
+ help="Set the ASGI 'root_path' for applications submounted below a given URL path.",
256
+ )
257
+ @click.option(
258
+ "--limit-concurrency",
259
+ type=int,
260
+ default=None,
261
+ help="Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses.",
262
+ )
263
+ @click.option(
264
+ "--backlog",
265
+ type=int,
266
+ default=2048,
267
+ help="Maximum number of connections to hold in backlog",
268
+ )
269
+ @click.option(
270
+ "--limit-max-requests",
271
+ type=int,
272
+ default=None,
273
+ help="Maximum number of requests to service before terminating the process.",
274
+ )
275
+ @click.option(
276
+ "--limit-max-requests-jitter",
277
+ type=int,
278
+ default=0,
279
+ help="Maximum jitter to add to limit_max_requests."
280
+ " Staggers worker restarts to avoid all workers restarting simultaneously.",
281
+ show_default=True,
282
+ )
283
+ @click.option(
284
+ "--timeout-keep-alive",
285
+ type=int,
286
+ default=5,
287
+ help="Close Keep-Alive connections if no new data is received within this timeout (in seconds).",
288
+ show_default=True,
289
+ )
290
+ @click.option(
291
+ "--timeout-graceful-shutdown",
292
+ type=int,
293
+ default=None,
294
+ help="Maximum number of seconds to wait for graceful shutdown.",
295
+ )
296
+ @click.option(
297
+ "--timeout-worker-healthcheck",
298
+ type=int,
299
+ default=5,
300
+ help="Maximum number of seconds to wait for a worker to respond to a healthcheck.",
301
+ show_default=True,
302
+ )
303
+ @click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True)
304
+ @click.option(
305
+ "--ssl-certfile",
306
+ type=str,
307
+ default=None,
308
+ help="SSL certificate file",
309
+ show_default=True,
310
+ )
311
+ @click.option(
312
+ "--ssl-keyfile-password",
313
+ type=str,
314
+ default=None,
315
+ help="SSL keyfile password",
316
+ show_default=True,
317
+ )
318
+ @click.option(
319
+ "--ssl-version",
320
+ type=int,
321
+ default=int(SSL_PROTOCOL_VERSION),
322
+ help="SSL version to use (see stdlib ssl module's)",
323
+ show_default=True,
324
+ )
325
+ @click.option(
326
+ "--ssl-cert-reqs",
327
+ type=int,
328
+ default=int(ssl.CERT_NONE),
329
+ help="Whether client certificate is required (see stdlib ssl module's)",
330
+ show_default=True,
331
+ )
332
+ @click.option(
333
+ "--ssl-ca-certs",
334
+ type=str,
335
+ default=None,
336
+ help="CA certificates file",
337
+ show_default=True,
338
+ )
339
+ @click.option(
340
+ "--ssl-ciphers",
341
+ type=str,
342
+ default=None,
343
+ help="Ciphers to use (see stdlib ssl module's). Defaults to OpenSSL's safe defaults.",
344
+ show_default=True,
345
+ )
346
+ @click.option(
347
+ "--header",
348
+ "headers",
349
+ multiple=True,
350
+ help="Specify custom default HTTP response headers as a Name:Value pair",
351
+ )
352
+ @click.option(
353
+ "--version",
354
+ is_flag=True,
355
+ callback=print_version,
356
+ expose_value=False,
357
+ is_eager=True,
358
+ help="Display the uvicorn version and exit.",
359
+ )
360
+ @click.option(
361
+ "--app-dir",
362
+ default="",
363
+ show_default=True,
364
+ help="Look for APP in the specified directory, by adding this to the PYTHONPATH."
365
+ " Defaults to the current working directory.",
366
+ )
367
+ @click.option(
368
+ "--h11-max-incomplete-event-size",
369
+ "h11_max_incomplete_event_size",
370
+ type=int,
371
+ default=None,
372
+ help="For h11, the maximum number of bytes to buffer of an incomplete event.",
373
+ )
374
+ @click.option(
375
+ "--reset-contextvars",
376
+ is_flag=True,
377
+ default=False,
378
+ help="Run each ASGI request in a fresh contextvars.Context. Hides context set in the lifespan.",
379
+ show_default=True,
380
+ )
381
+ @click.option(
382
+ "--factory",
383
+ is_flag=True,
384
+ default=False,
385
+ help="Treat APP as an application factory, i.e. a () -> <ASGI app> callable.",
386
+ show_default=True,
387
+ )
388
+ def main(
389
+ app: str,
390
+ host: str,
391
+ port: int,
392
+ uds: str,
393
+ fd: int,
394
+ loop: LoopFactoryType | str,
395
+ http: HTTPProtocolType | str,
396
+ ws: WSProtocolType | str,
397
+ ws_max_size: int,
398
+ ws_max_queue: int,
399
+ ws_ping_interval: float,
400
+ ws_ping_timeout: float,
401
+ ws_per_message_deflate: bool,
402
+ lifespan: LifespanType,
403
+ interface: InterfaceType,
404
+ reload: bool,
405
+ reload_dirs: list[str],
406
+ reload_includes: list[str],
407
+ reload_excludes: list[str],
408
+ reload_delay: float,
409
+ workers: int,
410
+ env_file: str,
411
+ log_config: str,
412
+ log_level: str,
413
+ access_log: bool,
414
+ proxy_headers: bool,
415
+ server_header: bool,
416
+ date_header: bool,
417
+ forwarded_allow_ips: str,
418
+ root_path: str,
419
+ limit_concurrency: int,
420
+ backlog: int,
421
+ limit_max_requests: int,
422
+ limit_max_requests_jitter: int,
423
+ timeout_keep_alive: int,
424
+ timeout_graceful_shutdown: int | None,
425
+ timeout_worker_healthcheck: int,
426
+ ssl_keyfile: str,
427
+ ssl_certfile: str,
428
+ ssl_keyfile_password: str,
429
+ ssl_version: int,
430
+ ssl_cert_reqs: int,
431
+ ssl_ca_certs: str,
432
+ ssl_ciphers: str | None,
433
+ headers: list[str],
434
+ use_colors: bool,
435
+ app_dir: str,
436
+ h11_max_incomplete_event_size: int | None,
437
+ reset_contextvars: bool,
438
+ factory: bool,
439
+ ) -> None:
440
+ run(
441
+ app,
442
+ host=host,
443
+ port=port,
444
+ uds=uds,
445
+ fd=fd,
446
+ loop=loop,
447
+ http=http,
448
+ ws=ws,
449
+ ws_max_size=ws_max_size,
450
+ ws_max_queue=ws_max_queue,
451
+ ws_ping_interval=ws_ping_interval,
452
+ ws_ping_timeout=ws_ping_timeout,
453
+ ws_per_message_deflate=ws_per_message_deflate,
454
+ lifespan=lifespan,
455
+ env_file=env_file,
456
+ log_config=LOGGING_CONFIG if log_config is None else log_config,
457
+ log_level=log_level,
458
+ access_log=access_log,
459
+ interface=interface,
460
+ reload=reload,
461
+ reload_dirs=reload_dirs or None,
462
+ reload_includes=reload_includes or None,
463
+ reload_excludes=reload_excludes or None,
464
+ reload_delay=reload_delay,
465
+ workers=workers,
466
+ proxy_headers=proxy_headers,
467
+ server_header=server_header,
468
+ date_header=date_header,
469
+ forwarded_allow_ips=forwarded_allow_ips,
470
+ root_path=root_path,
471
+ limit_concurrency=limit_concurrency,
472
+ backlog=backlog,
473
+ limit_max_requests=limit_max_requests,
474
+ limit_max_requests_jitter=limit_max_requests_jitter,
475
+ timeout_keep_alive=timeout_keep_alive,
476
+ timeout_graceful_shutdown=timeout_graceful_shutdown,
477
+ timeout_worker_healthcheck=timeout_worker_healthcheck,
478
+ ssl_keyfile=ssl_keyfile,
479
+ ssl_certfile=ssl_certfile,
480
+ ssl_keyfile_password=ssl_keyfile_password,
481
+ ssl_version=ssl_version,
482
+ ssl_cert_reqs=ssl_cert_reqs,
483
+ ssl_ca_certs=ssl_ca_certs,
484
+ ssl_ciphers=ssl_ciphers,
485
+ headers=[header.split(":", 1) for header in headers], # type: ignore[misc]
486
+ use_colors=use_colors,
487
+ factory=factory,
488
+ app_dir=app_dir,
489
+ h11_max_incomplete_event_size=h11_max_incomplete_event_size,
490
+ reset_contextvars=reset_contextvars,
491
+ )
492
+
493
+
494
+ def run(
495
+ app: ASGIApplication | Callable[..., Any] | str,
496
+ *,
497
+ host: str = "127.0.0.1",
498
+ port: int = 8000,
499
+ uds: str | None = None,
500
+ fd: int | None = None,
501
+ loop: LoopFactoryType | str = "auto",
502
+ http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto",
503
+ ws: type[asyncio.Protocol] | WSProtocolType | str = "auto",
504
+ ws_max_size: int = 16777216,
505
+ ws_max_queue: int = 32,
506
+ ws_ping_interval: float | None = 20.0,
507
+ ws_ping_timeout: float | None = 20.0,
508
+ ws_per_message_deflate: bool = True,
509
+ lifespan: LifespanType = "auto",
510
+ interface: InterfaceType = "auto",
511
+ reload: bool = False,
512
+ reload_dirs: list[str] | str | None = None,
513
+ reload_includes: list[str] | str | None = None,
514
+ reload_excludes: list[str] | str | None = None,
515
+ reload_delay: float = 0.25,
516
+ workers: int | None = None,
517
+ env_file: str | os.PathLike[str] | None = None,
518
+ log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG,
519
+ log_level: str | int | None = None,
520
+ access_log: bool = True,
521
+ proxy_headers: bool = True,
522
+ server_header: bool = True,
523
+ date_header: bool = True,
524
+ forwarded_allow_ips: list[str] | str | None = None,
525
+ root_path: str = "",
526
+ limit_concurrency: int | None = None,
527
+ backlog: int = 2048,
528
+ limit_max_requests: int | None = None,
529
+ limit_max_requests_jitter: int = 0,
530
+ timeout_keep_alive: int = 5,
531
+ timeout_graceful_shutdown: int | None = None,
532
+ timeout_worker_healthcheck: int = 5,
533
+ ssl_keyfile: str | os.PathLike[str] | None = None,
534
+ ssl_certfile: str | os.PathLike[str] | None = None,
535
+ ssl_keyfile_password: str | None = None,
536
+ ssl_version: int = SSL_PROTOCOL_VERSION,
537
+ ssl_cert_reqs: int = ssl.CERT_NONE,
538
+ ssl_ca_certs: str | os.PathLike[str] | None = None,
539
+ ssl_ciphers: str | None = None,
540
+ ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None,
541
+ headers: list[tuple[str, str]] | None = None,
542
+ use_colors: bool | None = None,
543
+ app_dir: str | None = None,
544
+ factory: bool = False,
545
+ h11_max_incomplete_event_size: int | None = None,
546
+ reset_contextvars: bool = False,
547
+ ) -> None:
548
+ if app_dir is not None:
549
+ sys.path.insert(0, app_dir)
550
+
551
+ config = Config(
552
+ app,
553
+ host=host,
554
+ port=port,
555
+ uds=uds,
556
+ fd=fd,
557
+ loop=loop,
558
+ http=http,
559
+ ws=ws,
560
+ ws_max_size=ws_max_size,
561
+ ws_max_queue=ws_max_queue,
562
+ ws_ping_interval=ws_ping_interval,
563
+ ws_ping_timeout=ws_ping_timeout,
564
+ ws_per_message_deflate=ws_per_message_deflate,
565
+ lifespan=lifespan,
566
+ interface=interface,
567
+ reload=reload,
568
+ reload_dirs=reload_dirs,
569
+ reload_includes=reload_includes,
570
+ reload_excludes=reload_excludes,
571
+ reload_delay=reload_delay,
572
+ workers=workers,
573
+ env_file=env_file,
574
+ log_config=log_config,
575
+ log_level=log_level,
576
+ access_log=access_log,
577
+ proxy_headers=proxy_headers,
578
+ server_header=server_header,
579
+ date_header=date_header,
580
+ forwarded_allow_ips=forwarded_allow_ips,
581
+ root_path=root_path,
582
+ limit_concurrency=limit_concurrency,
583
+ backlog=backlog,
584
+ limit_max_requests=limit_max_requests,
585
+ limit_max_requests_jitter=limit_max_requests_jitter,
586
+ timeout_keep_alive=timeout_keep_alive,
587
+ timeout_graceful_shutdown=timeout_graceful_shutdown,
588
+ timeout_worker_healthcheck=timeout_worker_healthcheck,
589
+ ssl_keyfile=ssl_keyfile,
590
+ ssl_certfile=ssl_certfile,
591
+ ssl_keyfile_password=ssl_keyfile_password,
592
+ ssl_version=ssl_version,
593
+ ssl_cert_reqs=ssl_cert_reqs,
594
+ ssl_ca_certs=ssl_ca_certs,
595
+ ssl_ciphers=ssl_ciphers,
596
+ ssl_context_factory=ssl_context_factory,
597
+ headers=headers,
598
+ use_colors=use_colors,
599
+ factory=factory,
600
+ h11_max_incomplete_event_size=h11_max_incomplete_event_size,
601
+ reset_contextvars=reset_contextvars,
602
+ )
603
+ if config.reload or config.workers > 1:
604
+ if not isinstance(app, str):
605
+ logger = logging.getLogger("uvicorn.error")
606
+ logger.warning("You must pass the application as an import string to enable 'reload' or 'workers'.")
607
+ sys.exit(STARTUP_FAILURE)
608
+ else:
609
+ config.load_app()
610
+
611
+ server = Server(config=config)
612
+
613
+ try:
614
+ if config.should_reload:
615
+ sock = config.bind_socket()
616
+ ChangeReload(config, target=server.run, sockets=[sock]).run()
617
+ elif config.workers > 1:
618
+ sock = config.bind_socket()
619
+ Multiprocess(config, sockets=[sock]).run()
620
+ else:
621
+ server.run()
622
+ except KeyboardInterrupt: # pragma: full coverage
623
+ pass
624
+ finally:
625
+ if config.uds and os.path.exists(config.uds):
626
+ os.remove(config.uds) # pragma: py-win32
627
+
628
+ if not server.started and not config.should_reload and config.workers == 1:
629
+ sys.exit(STARTUP_FAILURE)
630
+
631
+
632
+ def __getattr__(name: str) -> Any:
633
+ if name == "ServerState":
634
+ warnings.warn(
635
+ "uvicorn.main.ServerState is deprecated, use uvicorn.server.ServerState instead.",
636
+ DeprecationWarning,
637
+ )
638
+ from uvicorn.server import ServerState
639
+
640
+ return ServerState
641
+ raise AttributeError(f"module {__name__} has no attribute {name}")
642
+
643
+
644
+ if __name__ == "__main__":
645
+ main() # pragma: no cover
.venv/Lib/site-packages/uvicorn/middleware/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/middleware/asgi2.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from uvicorn._types import (
2
+ ASGI2Application,
3
+ ASGIReceiveCallable,
4
+ ASGISendCallable,
5
+ Scope,
6
+ )
7
+
8
+
9
+ class ASGI2Middleware:
10
+ def __init__(self, app: "ASGI2Application"):
11
+ self.app = app
12
+
13
+ async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable") -> None:
14
+ instance = self.app(scope)
15
+ await instance(receive, send)
.venv/Lib/site-packages/uvicorn/middleware/message_logger.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any
3
+
4
+ from uvicorn._types import (
5
+ ASGI3Application,
6
+ ASGIReceiveCallable,
7
+ ASGIReceiveEvent,
8
+ ASGISendCallable,
9
+ ASGISendEvent,
10
+ WWWScope,
11
+ )
12
+ from uvicorn.logging import TRACE_LOG_LEVEL
13
+
14
+ PLACEHOLDER_FORMAT = {
15
+ "body": "<{length} bytes>",
16
+ "bytes": "<{length} bytes>",
17
+ "text": "<{length} chars>",
18
+ "headers": "<...>",
19
+ }
20
+
21
+
22
+ def message_with_placeholders(message: Any) -> Any:
23
+ """
24
+ Return an ASGI message, with any body-type content omitted and replaced
25
+ with a placeholder.
26
+ """
27
+ new_message = message.copy()
28
+ for attr in PLACEHOLDER_FORMAT.keys():
29
+ if message.get(attr) is not None:
30
+ content = message[attr]
31
+ placeholder = PLACEHOLDER_FORMAT[attr].format(length=len(content))
32
+ new_message[attr] = placeholder
33
+ return new_message
34
+
35
+
36
+ class MessageLoggerMiddleware:
37
+ def __init__(self, app: "ASGI3Application"):
38
+ self.task_counter = 0
39
+ self.app = app
40
+ self.logger = logging.getLogger("uvicorn.asgi")
41
+
42
+ def trace(message: Any, *args: Any, **kwargs: Any) -> None:
43
+ self.logger.log(TRACE_LOG_LEVEL, message, *args, **kwargs)
44
+
45
+ self.logger.trace = trace # type: ignore
46
+
47
+ async def __call__(
48
+ self,
49
+ scope: "WWWScope",
50
+ receive: "ASGIReceiveCallable",
51
+ send: "ASGISendCallable",
52
+ ) -> None:
53
+ self.task_counter += 1
54
+
55
+ task_counter = self.task_counter
56
+ client = scope.get("client")
57
+ prefix = "%s:%d - ASGI" % (client[0], client[1]) if client else "ASGI"
58
+
59
+ async def inner_receive() -> "ASGIReceiveEvent":
60
+ message = await receive()
61
+ logged_message = message_with_placeholders(message)
62
+ log_text = "%s [%d] Receive %s"
63
+ self.logger.trace( # type: ignore
64
+ log_text, prefix, task_counter, logged_message
65
+ )
66
+ return message
67
+
68
+ async def inner_send(message: "ASGISendEvent") -> None:
69
+ logged_message = message_with_placeholders(message)
70
+ log_text = "%s [%d] Send %s"
71
+ self.logger.trace( # type: ignore
72
+ log_text, prefix, task_counter, logged_message
73
+ )
74
+ await send(message)
75
+
76
+ logged_scope = message_with_placeholders(scope)
77
+ log_text = "%s [%d] Started scope=%s"
78
+ self.logger.trace(log_text, prefix, task_counter, logged_scope) # type: ignore
79
+ try:
80
+ await self.app(scope, inner_receive, inner_send)
81
+ except BaseException as exc:
82
+ log_text = "%s [%d] Raised exception"
83
+ self.logger.trace(log_text, prefix, task_counter) # type: ignore
84
+ raise exc from None
85
+ else:
86
+ log_text = "%s [%d] Completed"
87
+ self.logger.trace(log_text, prefix, task_counter) # type: ignore
.venv/Lib/site-packages/uvicorn/middleware/proxy_headers.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import ipaddress
5
+
6
+ from uvicorn._types import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope
7
+
8
+
9
+ class ProxyHeadersMiddleware:
10
+ """Middleware for handling known proxy headers
11
+
12
+ This middleware can be used when a known proxy is fronting the application,
13
+ and is trusted to be properly setting the `X-Forwarded-Proto` and
14
+ `X-Forwarded-For` headers with the connecting client information.
15
+
16
+ Modifies the `client` and `scheme` information so that they reference
17
+ the connecting client, rather that the connecting proxy.
18
+
19
+ References:
20
+ - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Proxies>
21
+ - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For>
22
+ """
23
+
24
+ def __init__(self, app: ASGI3Application, trusted_hosts: list[str] | str = "127.0.0.1") -> None:
25
+ self.app = app
26
+ self.trusted_hosts = _TrustedHosts(trusted_hosts)
27
+
28
+ async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
29
+ if scope["type"] == "lifespan":
30
+ return await self.app(scope, receive, send)
31
+
32
+ client_addr = scope.get("client")
33
+ client_host = client_addr[0] if client_addr else None
34
+
35
+ if client_host in self.trusted_hosts:
36
+ x_forwarded_proto_value: bytes | None = None
37
+ x_forwarded_for_values: list[bytes] = []
38
+ for name, value in scope["headers"]:
39
+ if name == b"x-forwarded-proto":
40
+ x_forwarded_proto_value = value
41
+ elif name == b"x-forwarded-for":
42
+ x_forwarded_for_values.append(value)
43
+
44
+ if x_forwarded_proto_value is not None:
45
+ x_forwarded_proto = x_forwarded_proto_value.decode("latin1").strip()
46
+
47
+ if x_forwarded_proto in {"http", "https", "ws", "wss"}:
48
+ if scope["type"] == "websocket":
49
+ scope["scheme"] = x_forwarded_proto.replace("http", "ws")
50
+ else:
51
+ scope["scheme"] = x_forwarded_proto
52
+
53
+ if x_forwarded_for_values:
54
+ x_forwarded_for = b", ".join(x_forwarded_for_values).decode("latin1")
55
+ host, port = self.trusted_hosts.get_trusted_client_address(x_forwarded_for)
56
+
57
+ if host:
58
+ # If the x-forwarded-for header is empty then host is an empty string.
59
+ # Only set the client if we actually got something usable.
60
+ # See: https://github.com/Kludex/uvicorn/issues/1068
61
+ scope["client"] = (host, port)
62
+
63
+ return await self.app(scope, receive, send)
64
+
65
+
66
+ def _parse_raw_hosts(value: str) -> list[str]:
67
+ return [item.strip() for item in value.split(",")]
68
+
69
+
70
+ def _parse_host_port(value: str) -> tuple[str, int]:
71
+ """Parse a forwarded host value into host and optional port.
72
+
73
+ Accepts bare IPs, IPv4 `host:port`, and bracketed IPv6 `[host]:port`.
74
+ Any unrecognized or malformed value is treated conservatively and returned
75
+ without a port so trust checks do not silently normalize arbitrary input.
76
+ """
77
+
78
+ if value.startswith("["):
79
+ bracket_end = value.find("]")
80
+ if bracket_end == -1:
81
+ return value, 0
82
+
83
+ host = value[1:bracket_end]
84
+ remainder = value[bracket_end + 1 :]
85
+ if not remainder:
86
+ return host, 0
87
+ if not remainder.startswith(":"):
88
+ return value, 0
89
+
90
+ try:
91
+ return host, int(remainder[1:])
92
+ except ValueError:
93
+ return host, 0
94
+
95
+ if value.count(":") == 1:
96
+ host, port = value.rsplit(":", 1)
97
+ try:
98
+ return host, int(port)
99
+ except ValueError:
100
+ return value, 0
101
+
102
+ return value, 0
103
+
104
+
105
+ class _TrustedHosts:
106
+ """Container for trusted hosts and networks"""
107
+
108
+ def __init__(self, trusted_hosts: list[str] | str) -> None:
109
+ self.always_trust: bool = trusted_hosts in ("*", ["*"])
110
+
111
+ self.trusted_literals: set[str] = set()
112
+ self.trusted_hosts: set[ipaddress.IPv4Address | ipaddress.IPv6Address] = set()
113
+ self.trusted_networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set()
114
+
115
+ # Notes:
116
+ # - We separate hosts from literals as there are many ways to write
117
+ # an IPv6 Address so we need to compare by object.
118
+ # - We don't convert IP Address to single host networks (e.g. /32 / 128) as
119
+ # it more efficient to do an address lookup in a set than check for
120
+ # membership in each network.
121
+ # - We still allow literals as it might be possible that we receive a
122
+ # something that isn't an IP Address e.g. a unix socket.
123
+
124
+ if not self.always_trust:
125
+ if isinstance(trusted_hosts, str):
126
+ trusted_hosts = _parse_raw_hosts(trusted_hosts)
127
+
128
+ for host in trusted_hosts:
129
+ # Note: because we always convert invalid IP types to literals it
130
+ # is not possible for the user to know they provided a malformed IP
131
+ # type - this may lead to unexpected / difficult to debug behaviour.
132
+
133
+ if "/" in host:
134
+ # Looks like a network
135
+ try:
136
+ self.trusted_networks.add(ipaddress.ip_network(host))
137
+ except ValueError:
138
+ # Was not a valid IP Network
139
+ self.trusted_literals.add(host)
140
+ else:
141
+ try:
142
+ self.trusted_hosts.add(ipaddress.ip_address(host))
143
+ except ValueError:
144
+ # Was not a valid IP Address
145
+ self.trusted_literals.add(host)
146
+
147
+ self._trusts = functools.lru_cache(maxsize=4096)(self._compute_trust)
148
+
149
+ def __contains__(self, host: str | None) -> bool:
150
+ if self.always_trust:
151
+ return True
152
+
153
+ if not host:
154
+ return False
155
+
156
+ # Don't cache hosts longer than a DNS name (253); they can't be trusted and would pin huge cache keys.
157
+ if len(host) > 253:
158
+ return self._compute_trust(host) # pragma: no cover
159
+
160
+ return self._trusts(host)
161
+
162
+ def _compute_trust(self, host: str) -> bool:
163
+ try:
164
+ ip = ipaddress.ip_address(host)
165
+ return ip in self.trusted_hosts or any(ip in net for net in self.trusted_networks)
166
+ except ValueError:
167
+ return host in self.trusted_literals
168
+
169
+ def get_trusted_client_address(self, x_forwarded_for: str) -> tuple[str, int]:
170
+ """Extract the client address from x_forwarded_for header.
171
+
172
+ In general this is the first "untrusted" host in the forwarded for list.
173
+ """
174
+ x_forwarded_for_hosts = _parse_raw_hosts(x_forwarded_for)
175
+
176
+ if self.always_trust:
177
+ return _parse_host_port(x_forwarded_for_hosts[0])
178
+
179
+ # Note: each proxy appends to the header list so check it in reverse order
180
+ for host_port in reversed(x_forwarded_for_hosts):
181
+ host, port = _parse_host_port(host_port)
182
+ if host not in self:
183
+ return host, port
184
+
185
+ # All hosts are trusted meaning that the client was also a trusted proxy
186
+ # See https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576
187
+ return _parse_host_port(x_forwarded_for_hosts[0])
.venv/Lib/site-packages/uvicorn/middleware/wsgi.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import concurrent.futures
5
+ import io
6
+ import sys
7
+ import warnings
8
+ from collections import deque
9
+ from collections.abc import Iterable
10
+
11
+ from uvicorn._types import (
12
+ ASGIReceiveCallable,
13
+ ASGIReceiveEvent,
14
+ ASGISendCallable,
15
+ ASGISendEvent,
16
+ Environ,
17
+ ExcInfo,
18
+ HTTPRequestEvent,
19
+ HTTPResponseBodyEvent,
20
+ HTTPResponseStartEvent,
21
+ HTTPScope,
22
+ StartResponse,
23
+ WSGIApp,
24
+ )
25
+
26
+
27
+ def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO) -> Environ:
28
+ """
29
+ Builds a scope and request message into a WSGI environ object.
30
+ """
31
+ script_name = scope.get("root_path", "").encode("utf8").decode("latin1")
32
+ path_info = scope["path"].encode("utf8").decode("latin1")
33
+ if path_info.startswith(script_name):
34
+ path_info = path_info[len(script_name) :]
35
+ environ = {
36
+ "REQUEST_METHOD": scope["method"],
37
+ "SCRIPT_NAME": script_name,
38
+ "PATH_INFO": path_info,
39
+ "QUERY_STRING": scope["query_string"].decode("ascii"),
40
+ "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
41
+ "wsgi.version": (1, 0),
42
+ "wsgi.url_scheme": scope.get("scheme", "http"),
43
+ "wsgi.input": body,
44
+ "wsgi.errors": sys.stdout,
45
+ "wsgi.multithread": True,
46
+ "wsgi.multiprocess": True,
47
+ "wsgi.run_once": False,
48
+ }
49
+
50
+ # Get server name and port - required in WSGI, not in ASGI
51
+ server = scope.get("server")
52
+ if server is None:
53
+ server = ("localhost", 80)
54
+ environ["SERVER_NAME"] = server[0]
55
+ environ["SERVER_PORT"] = server[1]
56
+
57
+ # Get client IP address
58
+ client = scope.get("client")
59
+ if client is not None:
60
+ environ["REMOTE_ADDR"] = client[0]
61
+
62
+ # Go through headers and make them into environ entries
63
+ for name, value in scope.get("headers", []):
64
+ name_str: str = name.decode("latin1")
65
+ if name_str == "content-length":
66
+ corrected_name = "CONTENT_LENGTH"
67
+ elif name_str == "content-type":
68
+ corrected_name = "CONTENT_TYPE"
69
+ else:
70
+ corrected_name = "HTTP_%s" % name_str.upper().replace("-", "_")
71
+ # HTTPbis say only ASCII chars are allowed in headers, but we latin1
72
+ # just in case
73
+ value_str: str = value.decode("latin1")
74
+ if corrected_name in environ:
75
+ corrected_name_environ = environ[corrected_name]
76
+ assert isinstance(corrected_name_environ, str)
77
+ value_str = corrected_name_environ + "," + value_str
78
+ environ[corrected_name] = value_str
79
+ return environ
80
+
81
+
82
+ class _WSGIMiddleware:
83
+ def __init__(self, app: WSGIApp, workers: int = 10):
84
+ warnings.warn(
85
+ "Uvicorn's native WSGI implementation is deprecated, you should switch to a2wsgi (`pip install a2wsgi`).",
86
+ DeprecationWarning,
87
+ )
88
+ self.app = app
89
+ self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
90
+
91
+ async def __call__(
92
+ self,
93
+ scope: HTTPScope,
94
+ receive: ASGIReceiveCallable,
95
+ send: ASGISendCallable,
96
+ ) -> None:
97
+ assert scope["type"] == "http"
98
+ instance = WSGIResponder(self.app, self.executor, scope)
99
+ await instance(receive, send)
100
+
101
+
102
+ class WSGIResponder:
103
+ def __init__(
104
+ self,
105
+ app: WSGIApp,
106
+ executor: concurrent.futures.ThreadPoolExecutor,
107
+ scope: HTTPScope,
108
+ ):
109
+ self.app = app
110
+ self.executor = executor
111
+ self.scope = scope
112
+ self.status = None
113
+ self.response_headers = None
114
+ self.send_event = asyncio.Event()
115
+ self.send_queue: deque[ASGISendEvent | None] = deque()
116
+ self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
117
+ self.response_started = False
118
+ self.exc_info: ExcInfo | None = None
119
+
120
+ async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
121
+ message: HTTPRequestEvent = await receive() # type: ignore[assignment]
122
+ body = io.BytesIO(message.get("body", b""))
123
+ more_body = message.get("more_body", False)
124
+ if more_body:
125
+ body.seek(0, io.SEEK_END)
126
+ while more_body:
127
+ body_message: HTTPRequestEvent = (
128
+ await receive() # type: ignore[assignment]
129
+ )
130
+ body.write(body_message.get("body", b""))
131
+ more_body = body_message.get("more_body", False)
132
+ body.seek(0)
133
+ environ = build_environ(self.scope, message, body)
134
+ self.loop = asyncio.get_event_loop()
135
+ wsgi = self.loop.run_in_executor(self.executor, self.wsgi, environ, self.start_response)
136
+ sender = self.loop.create_task(self.sender(send))
137
+ try:
138
+ await asyncio.wait_for(wsgi, None)
139
+ finally:
140
+ self.send_queue.append(None)
141
+ self.send_event.set()
142
+ await asyncio.wait_for(sender, None)
143
+ if self.exc_info is not None:
144
+ raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
145
+
146
+ async def sender(self, send: ASGISendCallable) -> None:
147
+ while True:
148
+ if self.send_queue:
149
+ message = self.send_queue.popleft()
150
+ if message is None:
151
+ return
152
+ await send(message)
153
+ else: # pragma: no cover
154
+ await self.send_event.wait()
155
+ self.send_event.clear()
156
+
157
+ def start_response(
158
+ self,
159
+ status: str,
160
+ response_headers: Iterable[tuple[str, str]],
161
+ exc_info: ExcInfo | None = None,
162
+ ) -> None:
163
+ self.exc_info = exc_info
164
+ if not self.response_started:
165
+ self.response_started = True
166
+ status_code_str, _ = status.split(" ", 1)
167
+ status_code = int(status_code_str)
168
+ headers = [(name.encode("ascii"), value.encode("ascii")) for name, value in response_headers]
169
+ http_response_start_event: HTTPResponseStartEvent = {
170
+ "type": "http.response.start",
171
+ "status": status_code,
172
+ "headers": headers,
173
+ }
174
+ self.send_queue.append(http_response_start_event)
175
+ self.loop.call_soon_threadsafe(self.send_event.set)
176
+
177
+ def wsgi(self, environ: Environ, start_response: StartResponse) -> None:
178
+ for chunk in self.app(environ, start_response): # type: ignore
179
+ response_body: HTTPResponseBodyEvent = {
180
+ "type": "http.response.body",
181
+ "body": chunk,
182
+ "more_body": True,
183
+ }
184
+ self.send_queue.append(response_body)
185
+ self.loop.call_soon_threadsafe(self.send_event.set)
186
+
187
+ empty_body: HTTPResponseBodyEvent = {
188
+ "type": "http.response.body",
189
+ "body": b"",
190
+ "more_body": False,
191
+ }
192
+ self.send_queue.append(empty_body)
193
+ self.loop.call_soon_threadsafe(self.send_event.set)
194
+
195
+
196
+ try:
197
+ from a2wsgi import WSGIMiddleware
198
+ except ModuleNotFoundError: # pragma: no cover
199
+ WSGIMiddleware = _WSGIMiddleware # type: ignore[misc, assignment]
.venv/Lib/site-packages/uvicorn/protocols/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/protocols/http/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/protocols/http/auto.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ AutoHTTPProtocol: type[asyncio.Protocol]
6
+ try:
7
+ import httptools # noqa
8
+ except ImportError: # pragma: no cover
9
+ from uvicorn.protocols.http.h11_impl import H11Protocol
10
+
11
+ AutoHTTPProtocol = H11Protocol
12
+ else: # pragma: no cover
13
+ from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
14
+
15
+ AutoHTTPProtocol = HttpToolsProtocol
.venv/Lib/site-packages/uvicorn/protocols/http/flow_control.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+
3
+ from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
4
+
5
+ CLOSE_HEADER = (b"connection", b"close")
6
+
7
+ HIGH_WATER_LIMIT = 65536
8
+
9
+
10
+ class FlowControl:
11
+ def __init__(self, transport: asyncio.Transport) -> None:
12
+ self._transport = transport
13
+ self.read_paused = False
14
+ self.write_paused = False
15
+ self._is_writable_event = asyncio.Event()
16
+ self._is_writable_event.set()
17
+
18
+ async def drain(self) -> None:
19
+ await self._is_writable_event.wait() # pragma: full coverage
20
+
21
+ def pause_reading(self) -> None:
22
+ if not self.read_paused:
23
+ self.read_paused = True
24
+ self._transport.pause_reading()
25
+
26
+ def resume_reading(self) -> None:
27
+ if self.read_paused:
28
+ self.read_paused = False
29
+ self._transport.resume_reading()
30
+
31
+ def pause_writing(self) -> None:
32
+ if not self.write_paused: # pragma: full coverage
33
+ self.write_paused = True
34
+ self._is_writable_event.clear()
35
+
36
+ def resume_writing(self) -> None:
37
+ if self.write_paused: # pragma: full coverage
38
+ self.write_paused = False
39
+ self._is_writable_event.set()
40
+
41
+
42
+ async def service_unavailable(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
43
+ await send(
44
+ {
45
+ "type": "http.response.start",
46
+ "status": 503,
47
+ "headers": [
48
+ (b"content-type", b"text/plain; charset=utf-8"),
49
+ (b"content-length", b"19"),
50
+ (b"connection", b"close"),
51
+ ],
52
+ }
53
+ )
54
+ await send({"type": "http.response.body", "body": b"Service Unavailable", "more_body": False})
.venv/Lib/site-packages/uvicorn/protocols/http/h11_impl.py ADDED
@@ -0,0 +1,545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextvars
5
+ import http
6
+ import logging
7
+ import sys
8
+ from collections.abc import Callable
9
+ from typing import Any, Literal
10
+ from urllib.parse import unquote
11
+
12
+ import h11
13
+ from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE
14
+
15
+ from uvicorn._types import (
16
+ ASGI3Application,
17
+ ASGIReceiveEvent,
18
+ ASGISendEvent,
19
+ HTTPRequestEvent,
20
+ HTTPResponseBodyEvent,
21
+ HTTPResponseStartEvent,
22
+ HTTPScope,
23
+ )
24
+ from uvicorn.config import Config
25
+ from uvicorn.logging import TRACE_LOG_LEVEL
26
+ from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable
27
+ from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl
28
+ from uvicorn.server import ServerState
29
+
30
+
31
+ def _get_status_phrase(status_code: int) -> bytes:
32
+ try:
33
+ return http.HTTPStatus(status_code).phrase.encode()
34
+ except ValueError:
35
+ return b""
36
+
37
+
38
+ STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)}
39
+
40
+
41
+ class H11Protocol(asyncio.Protocol):
42
+ def __init__(
43
+ self,
44
+ config: Config,
45
+ server_state: ServerState,
46
+ app_state: dict[str, Any],
47
+ _loop: asyncio.AbstractEventLoop | None = None,
48
+ ) -> None:
49
+ if not config.loaded:
50
+ config.load()
51
+
52
+ self.config = config
53
+ self.app = config.loaded_app
54
+ self.loop = _loop or asyncio.get_event_loop()
55
+ self.logger = logging.getLogger("uvicorn.error")
56
+ self.access_logger = logging.getLogger("uvicorn.access")
57
+ self.access_log = self.access_logger.hasHandlers()
58
+ self.conn = h11.Connection(
59
+ h11.SERVER,
60
+ config.h11_max_incomplete_event_size
61
+ if config.h11_max_incomplete_event_size is not None
62
+ else DEFAULT_MAX_INCOMPLETE_EVENT_SIZE,
63
+ )
64
+ self.ws_protocol_class = config.ws_protocol_class
65
+ self.root_path = config.root_path
66
+ self.asgi_version = config.asgi_version
67
+ self.limit_concurrency = config.limit_concurrency
68
+ self.app_state = app_state
69
+
70
+ # Timeouts
71
+ self.timeout_keep_alive_task: asyncio.TimerHandle | None = None
72
+ self.timeout_keep_alive = config.timeout_keep_alive
73
+
74
+ # Shared server state
75
+ self.server_state = server_state
76
+ self.connections = server_state.connections
77
+ self.tasks = server_state.tasks
78
+
79
+ # Per-connection state
80
+ self.transport: asyncio.Transport = None # type: ignore[assignment]
81
+ self.flow: FlowControl = None # type: ignore[assignment]
82
+ self.server: tuple[str, int | None] | None = None
83
+ self.client: tuple[str, int] | None = None
84
+ self.scheme: Literal["http", "https"] | None = None
85
+
86
+ # Per-request state
87
+ self.scope: HTTPScope = None # type: ignore[assignment]
88
+ self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment]
89
+ self.cycle: RequestResponseCycle = None # type: ignore[assignment]
90
+
91
+ # Protocol interface
92
+ def connection_made( # type: ignore[override]
93
+ self, transport: asyncio.Transport
94
+ ) -> None:
95
+ self.connections.add(self)
96
+
97
+ self.transport = transport
98
+ self.flow = FlowControl(transport)
99
+ self.server = get_local_addr(transport)
100
+ self.client = get_remote_addr(transport)
101
+ self.scheme = "https" if is_ssl(transport) else "http"
102
+
103
+ if self.logger.level <= TRACE_LOG_LEVEL:
104
+ prefix = "%s:%d - " % self.client if self.client else ""
105
+ self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix)
106
+
107
+ def connection_lost(self, exc: Exception | None) -> None:
108
+ self.connections.discard(self)
109
+
110
+ if self.logger.level <= TRACE_LOG_LEVEL:
111
+ prefix = "%s:%d - " % self.client if self.client else ""
112
+ self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix)
113
+
114
+ if self.cycle and not self.cycle.response_complete:
115
+ self.cycle.disconnected = True
116
+ if self.conn.our_state != h11.ERROR:
117
+ event = h11.ConnectionClosed()
118
+ try:
119
+ self.conn.send(event)
120
+ except h11.LocalProtocolError:
121
+ # Premature client disconnect
122
+ pass
123
+
124
+ if self.cycle is not None:
125
+ self.cycle.message_event.set()
126
+ if self.flow is not None:
127
+ self.flow.resume_writing()
128
+ if exc is None:
129
+ self.transport.close()
130
+ self._unset_keepalive_if_required()
131
+
132
+ def eof_received(self) -> None:
133
+ pass
134
+
135
+ def _unset_keepalive_if_required(self) -> None:
136
+ if self.timeout_keep_alive_task is not None:
137
+ self.timeout_keep_alive_task.cancel()
138
+ self.timeout_keep_alive_task = None
139
+
140
+ def _get_upgrade(self) -> bytes | None:
141
+ connection = []
142
+ upgrade = None
143
+ for name, value in self.headers:
144
+ if name == b"connection":
145
+ connection = [token.lower().strip() for token in value.split(b",")]
146
+ if name == b"upgrade":
147
+ upgrade = value.lower()
148
+ if b"upgrade" in connection:
149
+ return upgrade
150
+ return None
151
+
152
+ def _should_upgrade_to_ws(self) -> bool:
153
+ if self.ws_protocol_class is None:
154
+ return False
155
+ return True
156
+
157
+ def _unsupported_upgrade_warning(self) -> None:
158
+ msg = "Unsupported upgrade request."
159
+ self.logger.warning(msg)
160
+ if not self._should_upgrade_to_ws():
161
+ msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501
162
+ self.logger.warning(msg)
163
+
164
+ def _should_upgrade(self) -> bool:
165
+ upgrade = self._get_upgrade()
166
+ if upgrade == b"websocket" and self._should_upgrade_to_ws():
167
+ return True
168
+ if upgrade is not None:
169
+ self._unsupported_upgrade_warning()
170
+ return False
171
+
172
+ def data_received(self, data: bytes) -> None:
173
+ self._unset_keepalive_if_required()
174
+
175
+ self.conn.receive_data(data)
176
+ self.handle_events()
177
+
178
+ def handle_events(self) -> None:
179
+ while True:
180
+ try:
181
+ event = self.conn.next_event()
182
+ except h11.RemoteProtocolError:
183
+ msg = "Invalid HTTP request received."
184
+ self.logger.warning(msg)
185
+ self.send_400_response(msg)
186
+ return
187
+
188
+ if event is h11.NEED_DATA:
189
+ break
190
+
191
+ elif event is h11.PAUSED:
192
+ # This case can occur in HTTP pipelining, so we need to
193
+ # stop reading any more data, and ensure that at the end
194
+ # of the active request/response cycle we handle any
195
+ # events that have been buffered up.
196
+ self.flow.pause_reading()
197
+ break
198
+
199
+ elif isinstance(event, h11.Request):
200
+ self.headers = [(key.lower(), value) for key, value in event.headers]
201
+ raw_path, _, query_string = event.target.partition(b"?")
202
+ path = unquote(raw_path.decode("ascii"))
203
+ full_path = self.root_path + path
204
+ full_raw_path = self.root_path.encode("ascii") + raw_path
205
+ self.scope = {
206
+ "type": "http",
207
+ "asgi": {"version": self.asgi_version, "spec_version": "2.3"},
208
+ "http_version": event.http_version.decode("ascii"),
209
+ "server": self.server,
210
+ "client": self.client,
211
+ "scheme": self.scheme, # type: ignore[typeddict-item]
212
+ "method": event.method.decode("ascii"),
213
+ "root_path": self.root_path,
214
+ "path": full_path,
215
+ "raw_path": full_raw_path,
216
+ "query_string": query_string,
217
+ "headers": self.headers,
218
+ "state": self.app_state.copy(),
219
+ }
220
+ if self._should_upgrade():
221
+ self.handle_websocket_upgrade(event)
222
+ return
223
+
224
+ # Handle 503 responses when 'limit_concurrency' is exceeded.
225
+ if self.limit_concurrency is not None and (
226
+ len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency
227
+ ):
228
+ app = service_unavailable
229
+ message = "Exceeded concurrency limit."
230
+ self.logger.warning(message)
231
+ else:
232
+ app = self.app
233
+
234
+ # When starting to process a request, disable the keep-alive
235
+ # timeout. Normally we disable this when receiving data from
236
+ # client and set back when finishing processing its request.
237
+ # However, for pipelined requests processing finishes after
238
+ # already receiving the next request and thus the timer may
239
+ # be set here, which we don't want.
240
+ self._unset_keepalive_if_required()
241
+
242
+ self.cycle = RequestResponseCycle(
243
+ scope=self.scope,
244
+ conn=self.conn,
245
+ transport=self.transport,
246
+ flow=self.flow,
247
+ logger=self.logger,
248
+ access_logger=self.access_logger,
249
+ access_log=self.access_log,
250
+ default_headers=self.server_state.default_headers,
251
+ message_event=asyncio.Event(),
252
+ on_response=self.on_response_complete,
253
+ )
254
+ if self.config.reset_contextvars:
255
+ # Opt-in workaround for https://github.com/python/cpython/issues/140947:
256
+ # asyncio can leak context vars between tasks. Hides context set in the
257
+ # lifespan or by external instrumentation.
258
+ if sys.version_info >= (3, 11): # pragma: py-lt-311
259
+ task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
260
+ else: # pragma: py-gte-311
261
+ task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
262
+ else:
263
+ task = self.loop.create_task(self.cycle.run_asgi(app))
264
+ task.add_done_callback(self.tasks.discard)
265
+ self.tasks.add(task)
266
+
267
+ elif isinstance(event, h11.Data):
268
+ if self.conn.our_state is h11.DONE:
269
+ continue
270
+ self.cycle.body += event.data
271
+ if len(self.cycle.body) > HIGH_WATER_LIMIT:
272
+ self.flow.pause_reading()
273
+ self.cycle.message_event.set()
274
+
275
+ elif isinstance(event, h11.EndOfMessage):
276
+ if self.conn.our_state is h11.DONE:
277
+ self.transport.resume_reading()
278
+ self.conn.start_next_cycle()
279
+ continue
280
+ self.cycle.more_body = False
281
+ self.cycle.message_event.set()
282
+ if self.conn.their_state == h11.MUST_CLOSE:
283
+ break
284
+
285
+ def handle_websocket_upgrade(self, event: h11.Request) -> None:
286
+ if self.logger.level <= TRACE_LOG_LEVEL: # pragma: full coverage
287
+ prefix = "%s:%d - " % self.client if self.client else ""
288
+ self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix)
289
+
290
+ self.connections.discard(self)
291
+ output = [event.method, b" ", event.target, b" HTTP/1.1\r\n"]
292
+ for name, value in self.headers:
293
+ output += [name, b": ", value, b"\r\n"]
294
+ output.append(b"\r\n")
295
+ protocol = self.ws_protocol_class( # type: ignore[call-arg, misc]
296
+ config=self.config,
297
+ server_state=self.server_state,
298
+ app_state=self.app_state,
299
+ )
300
+ protocol.connection_made(self.transport)
301
+ protocol.data_received(b"".join(output))
302
+ self.transport.set_protocol(protocol)
303
+
304
+ def send_400_response(self, msg: str) -> None:
305
+ reason = STATUS_PHRASES[400]
306
+ headers: list[tuple[bytes, bytes]] = [
307
+ (b"content-type", b"text/plain; charset=utf-8"),
308
+ (b"connection", b"close"),
309
+ ]
310
+ event = h11.Response(status_code=400, headers=headers, reason=reason)
311
+ output = self.conn.send(event)
312
+ self.transport.write(output)
313
+
314
+ output = self.conn.send(event=h11.Data(data=msg.encode("ascii")))
315
+ self.transport.write(output)
316
+
317
+ output = self.conn.send(event=h11.EndOfMessage())
318
+ self.transport.write(output)
319
+
320
+ self.transport.close()
321
+
322
+ def on_response_complete(self) -> None:
323
+ self.server_state.total_requests += 1
324
+
325
+ if self.transport.is_closing():
326
+ return
327
+
328
+ # Set a short Keep-Alive timeout.
329
+ self._unset_keepalive_if_required()
330
+
331
+ self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler)
332
+
333
+ # Unpause data reads if needed.
334
+ self.flow.resume_reading()
335
+
336
+ # Unblock any pipelined events.
337
+ if self.conn.our_state is h11.DONE and self.conn.their_state is h11.DONE:
338
+ self.conn.start_next_cycle()
339
+ self.handle_events()
340
+
341
+ def shutdown(self) -> None:
342
+ """
343
+ Called by the server to commence a graceful shutdown.
344
+ """
345
+ if self.cycle is None or self.cycle.response_complete:
346
+ event = h11.ConnectionClosed()
347
+ self.conn.send(event)
348
+ self.transport.close()
349
+ else:
350
+ self.cycle.keep_alive = False
351
+
352
+ def pause_writing(self) -> None:
353
+ """
354
+ Called by the transport when the write buffer exceeds the high water mark.
355
+ """
356
+ self.flow.pause_writing() # pragma: full coverage
357
+
358
+ def resume_writing(self) -> None:
359
+ """
360
+ Called by the transport when the write buffer drops below the low water mark.
361
+ """
362
+ self.flow.resume_writing() # pragma: full coverage
363
+
364
+ def timeout_keep_alive_handler(self) -> None:
365
+ """
366
+ Called on a keep-alive connection if no new data is received after a short
367
+ delay.
368
+ """
369
+ if not self.transport.is_closing():
370
+ event = h11.ConnectionClosed()
371
+ self.conn.send(event)
372
+ self.transport.close()
373
+
374
+
375
+ class RequestResponseCycle:
376
+ def __init__(
377
+ self,
378
+ scope: HTTPScope,
379
+ conn: h11.Connection,
380
+ transport: asyncio.Transport,
381
+ flow: FlowControl,
382
+ logger: logging.Logger,
383
+ access_logger: logging.Logger,
384
+ access_log: bool,
385
+ default_headers: list[tuple[bytes, bytes]],
386
+ message_event: asyncio.Event,
387
+ on_response: Callable[..., None],
388
+ ) -> None:
389
+ self.scope = scope
390
+ self.conn = conn
391
+ self.transport = transport
392
+ self.flow = flow
393
+ self.logger = logger
394
+ self.access_logger = access_logger
395
+ self.access_log = access_log
396
+ self.default_headers = default_headers
397
+ self.message_event = message_event
398
+ self.on_response = on_response
399
+
400
+ # Connection state
401
+ self.disconnected = False
402
+ self.keep_alive = True
403
+ self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue
404
+
405
+ # Request state
406
+ self.body = bytearray()
407
+ self.more_body = True
408
+
409
+ # Response state
410
+ self.response_started = False
411
+ self.response_complete = False
412
+
413
+ # ASGI exception wrapper
414
+ async def run_asgi(self, app: ASGI3Application) -> None:
415
+ try:
416
+ result = await app( # type: ignore[func-returns-value]
417
+ self.scope, self.receive, self.send
418
+ )
419
+ except BaseException as exc:
420
+ msg = "Exception in ASGI application\n"
421
+ self.logger.error(msg, exc_info=exc)
422
+ if not self.response_started:
423
+ await self.send_500_response()
424
+ else:
425
+ self.transport.close()
426
+ else:
427
+ if result is not None:
428
+ msg = "ASGI callable should return None, but returned '%s'."
429
+ self.logger.error(msg, result)
430
+ self.transport.close()
431
+ elif not self.response_started and not self.disconnected:
432
+ msg = "ASGI callable returned without starting response."
433
+ self.logger.error(msg)
434
+ await self.send_500_response()
435
+ elif not self.response_complete and not self.disconnected:
436
+ msg = "ASGI callable returned without completing response."
437
+ self.logger.error(msg)
438
+ self.transport.close()
439
+ finally:
440
+ self.on_response = lambda: None
441
+
442
+ async def send_500_response(self) -> None:
443
+ response_start_event: HTTPResponseStartEvent = {
444
+ "type": "http.response.start",
445
+ "status": 500,
446
+ "headers": [
447
+ (b"content-type", b"text/plain; charset=utf-8"),
448
+ (b"connection", b"close"),
449
+ ],
450
+ }
451
+ await self.send(response_start_event)
452
+ response_body_event: HTTPResponseBodyEvent = {
453
+ "type": "http.response.body",
454
+ "body": b"Internal Server Error",
455
+ "more_body": False,
456
+ }
457
+ await self.send(response_body_event)
458
+
459
+ # ASGI interface
460
+ async def send(self, message: ASGISendEvent) -> None:
461
+ if self.flow.write_paused and not self.disconnected:
462
+ await self.flow.drain() # pragma: full coverage
463
+
464
+ if self.disconnected:
465
+ return # pragma: full coverage
466
+
467
+ if not self.response_started:
468
+ # Sending response status line and headers
469
+ if message["type"] != "http.response.start":
470
+ raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.")
471
+
472
+ self.response_started = True
473
+ self.waiting_for_100_continue = False
474
+
475
+ status = message["status"]
476
+ headers = self.default_headers + list(message.get("headers", []))
477
+
478
+ if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers:
479
+ headers = headers + [CLOSE_HEADER]
480
+
481
+ if self.access_log:
482
+ self.access_logger.info(
483
+ '%s - "%s %s HTTP/%s" %d',
484
+ get_client_addr(self.scope),
485
+ self.scope["method"],
486
+ get_path_with_query_string(self.scope),
487
+ self.scope["http_version"],
488
+ status,
489
+ )
490
+
491
+ # Write response status line and headers
492
+ reason = STATUS_PHRASES[status]
493
+ response = h11.Response(status_code=status, headers=headers, reason=reason)
494
+ output = self.conn.send(event=response)
495
+ self.transport.write(output)
496
+
497
+ elif not self.response_complete:
498
+ # Sending response body
499
+ if message["type"] != "http.response.body":
500
+ raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.")
501
+
502
+ body = message.get("body", b"")
503
+ more_body = message.get("more_body", False)
504
+
505
+ # Write response body
506
+ data = b"" if self.scope["method"] == "HEAD" else body
507
+ output = self.conn.send(event=h11.Data(data=data))
508
+ self.transport.write(output)
509
+
510
+ # Handle response completion
511
+ if not more_body:
512
+ self.response_complete = True
513
+ self.message_event.set()
514
+ output = self.conn.send(event=h11.EndOfMessage())
515
+ self.transport.write(output)
516
+
517
+ else:
518
+ # Response already sent
519
+ raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.")
520
+
521
+ if self.response_complete:
522
+ if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive:
523
+ self.conn.send(event=h11.ConnectionClosed())
524
+ self.transport.close()
525
+ self.on_response()
526
+
527
+ async def receive(self) -> ASGIReceiveEvent:
528
+ if self.waiting_for_100_continue and not self.transport.is_closing():
529
+ headers: list[tuple[str, str]] = []
530
+ event = h11.InformationalResponse(status_code=100, headers=headers, reason="Continue")
531
+ output = self.conn.send(event=event)
532
+ self.transport.write(output)
533
+ self.waiting_for_100_continue = False
534
+
535
+ if not self.disconnected and not self.response_complete:
536
+ self.flow.resume_reading()
537
+ await self.message_event.wait()
538
+ self.message_event.clear()
539
+
540
+ if self.disconnected or self.response_complete:
541
+ return {"type": "http.disconnect"}
542
+
543
+ message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body}
544
+ self.body = bytearray()
545
+ return message
.venv/Lib/site-packages/uvicorn/protocols/http/httptools_impl.py ADDED
@@ -0,0 +1,577 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextvars
5
+ import http
6
+ import logging
7
+ import re
8
+ import sys
9
+ import urllib
10
+ from asyncio.events import TimerHandle
11
+ from collections import deque
12
+ from collections.abc import Callable
13
+ from typing import Any, Literal
14
+
15
+ import httptools
16
+
17
+ from uvicorn._types import (
18
+ ASGI3Application,
19
+ ASGIReceiveEvent,
20
+ ASGISendEvent,
21
+ HTTPRequestEvent,
22
+ HTTPScope,
23
+ )
24
+ from uvicorn.config import Config
25
+ from uvicorn.logging import TRACE_LOG_LEVEL
26
+ from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable
27
+ from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl
28
+ from uvicorn.server import ServerState
29
+
30
+ HEADER_RE = re.compile(b'[\x00-\x1f\x7f()<>@,;:\\[\\]={} \t\\\\"]')
31
+ HEADER_VALUE_RE = re.compile(b"[\x00-\x08\x0a-\x1f\x7f]")
32
+
33
+
34
+ def _get_status_line(status_code: int) -> bytes:
35
+ try:
36
+ phrase = http.HTTPStatus(status_code).phrase.encode()
37
+ except ValueError:
38
+ phrase = b""
39
+ return b"".join([b"HTTP/1.1 ", str(status_code).encode(), b" ", phrase, b"\r\n"])
40
+
41
+
42
+ STATUS_LINE = {status_code: _get_status_line(status_code) for status_code in range(100, 600)}
43
+
44
+
45
+ class HttpToolsProtocol(asyncio.Protocol):
46
+ def __init__(
47
+ self,
48
+ config: Config,
49
+ server_state: ServerState,
50
+ app_state: dict[str, Any],
51
+ _loop: asyncio.AbstractEventLoop | None = None,
52
+ ) -> None:
53
+ if not config.loaded:
54
+ config.load()
55
+
56
+ self.config = config
57
+ self.app = config.loaded_app
58
+ self.loop = _loop or asyncio.get_event_loop()
59
+ self.logger = logging.getLogger("uvicorn.error")
60
+ self.access_logger = logging.getLogger("uvicorn.access")
61
+ self.access_log = self.access_logger.hasHandlers()
62
+ self.parser = httptools.HttpRequestParser(self)
63
+
64
+ try:
65
+ # Enable dangerous leniencies to allow server to a response on the first request from a pipelined request.
66
+ self.parser.set_dangerous_leniencies(lenient_data_after_close=True)
67
+ except AttributeError: # pragma: no cover
68
+ # httptools < 0.6.3
69
+ pass
70
+
71
+ self.ws_protocol_class = config.ws_protocol_class
72
+ self.root_path = config.root_path
73
+ self.asgi_version = config.asgi_version
74
+ self.limit_concurrency = config.limit_concurrency
75
+ self.app_state = app_state
76
+
77
+ # Timeouts
78
+ self.timeout_keep_alive_task: TimerHandle | None = None
79
+ self.timeout_keep_alive = config.timeout_keep_alive
80
+
81
+ # Global state
82
+ self.server_state = server_state
83
+ self.connections = server_state.connections
84
+ self.tasks = server_state.tasks
85
+
86
+ # Per-connection state
87
+ self.transport: asyncio.Transport = None # type: ignore[assignment]
88
+ self.flow: FlowControl = None # type: ignore[assignment]
89
+ self.server: tuple[str, int | None] | None = None
90
+ self.client: tuple[str, int] | None = None
91
+ self.scheme: Literal["http", "https"] | None = None
92
+ self.pipeline: deque[tuple[RequestResponseCycle, ASGI3Application]] = deque()
93
+
94
+ # Per-request state
95
+ self.scope: HTTPScope = None # type: ignore[assignment]
96
+ self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment]
97
+ self.expect_100_continue = False
98
+ self.cycle: RequestResponseCycle = None # type: ignore[assignment]
99
+
100
+ # Protocol interface
101
+ def connection_made( # type: ignore[override]
102
+ self, transport: asyncio.Transport
103
+ ) -> None:
104
+ self.connections.add(self)
105
+
106
+ self.transport = transport
107
+ self.flow = FlowControl(transport)
108
+ self.server = get_local_addr(transport)
109
+ self.client = get_remote_addr(transport)
110
+ self.scheme = "https" if is_ssl(transport) else "http"
111
+
112
+ if self.logger.level <= TRACE_LOG_LEVEL:
113
+ prefix = "%s:%d - " % self.client if self.client else ""
114
+ self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix)
115
+
116
+ def connection_lost(self, exc: Exception | None) -> None:
117
+ self.connections.discard(self)
118
+
119
+ if self.logger.level <= TRACE_LOG_LEVEL:
120
+ prefix = "%s:%d - " % self.client if self.client else ""
121
+ self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix)
122
+
123
+ if self.cycle and not self.cycle.response_complete:
124
+ self.cycle.disconnected = True
125
+ if self.cycle is not None:
126
+ self.cycle.message_event.set()
127
+ if self.flow is not None:
128
+ self.flow.resume_writing()
129
+ if exc is None:
130
+ self.transport.close()
131
+ self._unset_keepalive_if_required()
132
+
133
+ self.parser = None # type: ignore[assignment]
134
+
135
+ def eof_received(self) -> None:
136
+ pass
137
+
138
+ def _unset_keepalive_if_required(self) -> None:
139
+ if self.timeout_keep_alive_task is not None:
140
+ self.timeout_keep_alive_task.cancel()
141
+ self.timeout_keep_alive_task = None
142
+
143
+ def _get_upgrade(self) -> bytes | None:
144
+ connection = []
145
+ upgrade = None
146
+ for name, value in self.headers:
147
+ if name == b"connection":
148
+ connection = [token.lower().strip() for token in value.split(b",")]
149
+ if name == b"upgrade":
150
+ upgrade = value.lower()
151
+ if b"upgrade" in connection:
152
+ return upgrade
153
+ return None # pragma: full coverage
154
+
155
+ def _should_upgrade_to_ws(self) -> bool:
156
+ if self.ws_protocol_class is None:
157
+ return False
158
+ return True
159
+
160
+ def _unsupported_upgrade_warning(self) -> None:
161
+ self.logger.warning("Unsupported upgrade request.")
162
+ if not self._should_upgrade_to_ws():
163
+ msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501
164
+ self.logger.warning(msg)
165
+
166
+ def _should_upgrade(self) -> bool:
167
+ upgrade = self._get_upgrade()
168
+ return upgrade == b"websocket" and self._should_upgrade_to_ws()
169
+
170
+ def data_received(self, data: bytes) -> None:
171
+ self._unset_keepalive_if_required()
172
+
173
+ try:
174
+ self.parser.feed_data(data)
175
+ except httptools.HttpParserError:
176
+ msg = "Invalid HTTP request received."
177
+ self.logger.warning(msg)
178
+ self.send_400_response(msg)
179
+ return
180
+ except httptools.HttpParserUpgrade:
181
+ if self._should_upgrade():
182
+ self.handle_websocket_upgrade()
183
+ else:
184
+ self._unsupported_upgrade_warning()
185
+
186
+ def handle_websocket_upgrade(self) -> None:
187
+ if self.logger.level <= TRACE_LOG_LEVEL:
188
+ prefix = "%s:%d - " % self.client if self.client else ""
189
+ self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix)
190
+
191
+ self.connections.discard(self)
192
+ method = self.scope["method"].encode()
193
+ output = [method, b" ", self.url, b" HTTP/1.1\r\n"]
194
+ for name, value in self.scope["headers"]:
195
+ output += [name, b": ", value, b"\r\n"]
196
+ output.append(b"\r\n")
197
+ protocol = self.ws_protocol_class( # type: ignore[call-arg, misc]
198
+ config=self.config,
199
+ server_state=self.server_state,
200
+ app_state=self.app_state,
201
+ )
202
+ protocol.connection_made(self.transport)
203
+ protocol.data_received(b"".join(output))
204
+ self.transport.set_protocol(protocol)
205
+
206
+ def send_400_response(self, msg: str) -> None:
207
+ content = [STATUS_LINE[400]]
208
+ for name, value in self.server_state.default_headers:
209
+ content.extend([name, b": ", value, b"\r\n"]) # pragma: full coverage
210
+ content.extend(
211
+ [
212
+ b"content-type: text/plain; charset=utf-8\r\n",
213
+ b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n",
214
+ b"connection: close\r\n",
215
+ b"\r\n",
216
+ msg.encode("ascii"),
217
+ ]
218
+ )
219
+ self.transport.write(b"".join(content))
220
+ self.transport.close()
221
+
222
+ def on_message_begin(self) -> None:
223
+ self.url = b""
224
+ self.expect_100_continue = False
225
+ self.headers = []
226
+ self.scope = { # type: ignore[typeddict-item]
227
+ "type": "http",
228
+ "asgi": {"version": self.asgi_version, "spec_version": "2.3"},
229
+ "http_version": "1.1",
230
+ "server": self.server,
231
+ "client": self.client,
232
+ "scheme": self.scheme, # type: ignore[typeddict-item]
233
+ "root_path": self.root_path,
234
+ "headers": self.headers,
235
+ "state": self.app_state.copy(),
236
+ }
237
+
238
+ # Parser callbacks
239
+ def on_url(self, url: bytes) -> None:
240
+ self.url += url
241
+
242
+ def on_header(self, name: bytes, value: bytes) -> None:
243
+ name = name.lower()
244
+ if name == b"expect" and value.lower() == b"100-continue":
245
+ self.expect_100_continue = True
246
+ self.headers.append((name, value))
247
+
248
+ def on_headers_complete(self) -> None:
249
+ http_version = self.parser.get_http_version()
250
+ method = self.parser.get_method()
251
+ self.scope["method"] = method.decode("ascii")
252
+ if http_version != "1.1":
253
+ self.scope["http_version"] = http_version
254
+ if self.parser.should_upgrade() and self._should_upgrade():
255
+ return
256
+ parsed_url = httptools.parse_url(self.url)
257
+ raw_path = parsed_url.path
258
+ path = raw_path.decode("ascii")
259
+ if "%" in path:
260
+ path = urllib.parse.unquote(path)
261
+ full_path = self.root_path + path
262
+ full_raw_path = self.root_path.encode("ascii") + raw_path
263
+ self.scope["path"] = full_path
264
+ self.scope["raw_path"] = full_raw_path
265
+ self.scope["query_string"] = parsed_url.query or b""
266
+
267
+ # Handle 503 responses when 'limit_concurrency' is exceeded.
268
+ if self.limit_concurrency is not None and (
269
+ len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency
270
+ ):
271
+ app = service_unavailable
272
+ message = "Exceeded concurrency limit."
273
+ self.logger.warning(message)
274
+ else:
275
+ app = self.app
276
+
277
+ existing_cycle = self.cycle
278
+ self.cycle = RequestResponseCycle(
279
+ scope=self.scope,
280
+ transport=self.transport,
281
+ flow=self.flow,
282
+ logger=self.logger,
283
+ access_logger=self.access_logger,
284
+ access_log=self.access_log,
285
+ default_headers=self.server_state.default_headers,
286
+ message_event=asyncio.Event(),
287
+ expect_100_continue=self.expect_100_continue,
288
+ keep_alive=http_version != "1.0",
289
+ on_response=self.on_response_complete,
290
+ )
291
+ if existing_cycle is None or existing_cycle.response_complete:
292
+ # Standard case - start processing the request.
293
+ self._start_asgi_task(self.cycle, app)
294
+ else:
295
+ # Pipelined HTTP requests need to be queued up.
296
+ self.flow.pause_reading()
297
+ self.pipeline.appendleft((self.cycle, app))
298
+
299
+ def _start_asgi_task(self, cycle: RequestResponseCycle, app: ASGI3Application) -> None:
300
+ if self.config.reset_contextvars:
301
+ # Opt-in workaround for https://github.com/python/cpython/issues/140947:
302
+ # asyncio can leak context vars between tasks. Hides context set in the
303
+ # lifespan or by external instrumentation.
304
+ if sys.version_info >= (3, 11): # pragma: py-lt-311
305
+ task = self.loop.create_task(cycle.run_asgi(app), context=contextvars.Context())
306
+ else: # pragma: py-gte-311
307
+ task = contextvars.Context().run(self.loop.create_task, cycle.run_asgi(app))
308
+ else:
309
+ task = self.loop.create_task(cycle.run_asgi(app))
310
+ task.add_done_callback(self.tasks.discard)
311
+ self.tasks.add(task)
312
+
313
+ def on_body(self, body: bytes) -> None:
314
+ if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:
315
+ return
316
+ self.cycle.body += body
317
+ if len(self.cycle.body) > HIGH_WATER_LIMIT:
318
+ self.flow.pause_reading()
319
+ self.cycle.message_event.set()
320
+
321
+ def on_message_complete(self) -> None:
322
+ if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:
323
+ return
324
+ self.cycle.more_body = False
325
+ self.cycle.message_event.set()
326
+
327
+ def on_response_complete(self) -> None:
328
+ # Callback for pipelined HTTP requests to be started.
329
+ self.server_state.total_requests += 1
330
+
331
+ if self.transport.is_closing():
332
+ return
333
+
334
+ self._unset_keepalive_if_required()
335
+
336
+ # Unpause data reads if needed.
337
+ self.flow.resume_reading()
338
+
339
+ # Unblock any pipelined events. If there are none, arm the
340
+ # Keep-Alive timeout instead.
341
+ if self.pipeline:
342
+ cycle, app = self.pipeline.pop()
343
+ self._start_asgi_task(cycle, app)
344
+ else:
345
+ self.timeout_keep_alive_task = self.loop.call_later(
346
+ self.timeout_keep_alive, self.timeout_keep_alive_handler
347
+ )
348
+
349
+ def shutdown(self) -> None:
350
+ """
351
+ Called by the server to commence a graceful shutdown.
352
+ """
353
+ if self.cycle is None or self.cycle.response_complete:
354
+ self.transport.close()
355
+ else:
356
+ self.cycle.keep_alive = False
357
+
358
+ def pause_writing(self) -> None:
359
+ """
360
+ Called by the transport when the write buffer exceeds the high water mark.
361
+ """
362
+ self.flow.pause_writing() # pragma: full coverage
363
+
364
+ def resume_writing(self) -> None:
365
+ """
366
+ Called by the transport when the write buffer drops below the low water mark.
367
+ """
368
+ self.flow.resume_writing() # pragma: full coverage
369
+
370
+ def timeout_keep_alive_handler(self) -> None:
371
+ """
372
+ Called on a keep-alive connection if no new data is received after a short
373
+ delay.
374
+ """
375
+ if not self.transport.is_closing():
376
+ self.transport.close()
377
+
378
+
379
+ class RequestResponseCycle:
380
+ def __init__(
381
+ self,
382
+ scope: HTTPScope,
383
+ transport: asyncio.Transport,
384
+ flow: FlowControl,
385
+ logger: logging.Logger,
386
+ access_logger: logging.Logger,
387
+ access_log: bool,
388
+ default_headers: list[tuple[bytes, bytes]],
389
+ message_event: asyncio.Event,
390
+ expect_100_continue: bool,
391
+ keep_alive: bool,
392
+ on_response: Callable[..., None],
393
+ ):
394
+ self.scope = scope
395
+ self.transport = transport
396
+ self.flow = flow
397
+ self.logger = logger
398
+ self.access_logger = access_logger
399
+ self.access_log = access_log
400
+ self.default_headers = default_headers
401
+ self.message_event = message_event
402
+ self.on_response = on_response
403
+
404
+ # Connection state
405
+ self.disconnected = False
406
+ self.keep_alive = keep_alive
407
+ self.waiting_for_100_continue = expect_100_continue
408
+
409
+ # Request state
410
+ self.body = bytearray()
411
+ self.more_body = True
412
+
413
+ # Response state
414
+ self.response_started = False
415
+ self.response_complete = False
416
+ self.chunked_encoding: bool | None = None
417
+ self.expected_content_length = 0
418
+
419
+ # ASGI exception wrapper
420
+ async def run_asgi(self, app: ASGI3Application) -> None:
421
+ try:
422
+ result = await app( # type: ignore[func-returns-value]
423
+ self.scope, self.receive, self.send
424
+ )
425
+ except BaseException as exc:
426
+ msg = "Exception in ASGI application\n"
427
+ self.logger.error(msg, exc_info=exc)
428
+ if not self.response_started:
429
+ await self.send_500_response()
430
+ else:
431
+ self.transport.close()
432
+ else:
433
+ if result is not None:
434
+ msg = "ASGI callable should return None, but returned '%s'."
435
+ self.logger.error(msg, result)
436
+ self.transport.close()
437
+ elif not self.response_started and not self.disconnected:
438
+ msg = "ASGI callable returned without starting response."
439
+ self.logger.error(msg)
440
+ await self.send_500_response()
441
+ elif not self.response_complete and not self.disconnected:
442
+ msg = "ASGI callable returned without completing response."
443
+ self.logger.error(msg)
444
+ self.transport.close()
445
+ finally:
446
+ self.on_response = lambda: None
447
+
448
+ async def send_500_response(self) -> None:
449
+ await self.send(
450
+ {
451
+ "type": "http.response.start",
452
+ "status": 500,
453
+ "headers": [
454
+ (b"content-type", b"text/plain; charset=utf-8"),
455
+ (b"content-length", b"21"),
456
+ (b"connection", b"close"),
457
+ ],
458
+ }
459
+ )
460
+ await self.send({"type": "http.response.body", "body": b"Internal Server Error", "more_body": False})
461
+
462
+ # ASGI interface
463
+ async def send(self, message: ASGISendEvent) -> None:
464
+ if self.flow.write_paused and not self.disconnected:
465
+ await self.flow.drain() # pragma: full coverage
466
+
467
+ if self.disconnected:
468
+ return # pragma: full coverage
469
+
470
+ if not self.response_started:
471
+ # Sending response status line and headers
472
+ if message["type"] != "http.response.start":
473
+ raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.")
474
+
475
+ self.response_started = True
476
+ self.waiting_for_100_continue = False
477
+
478
+ status_code = message["status"]
479
+ headers = self.default_headers + list(message.get("headers", []))
480
+
481
+ if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers:
482
+ headers = headers + [CLOSE_HEADER]
483
+
484
+ if self.access_log:
485
+ self.access_logger.info(
486
+ '%s - "%s %s HTTP/%s" %d',
487
+ get_client_addr(self.scope),
488
+ self.scope["method"],
489
+ get_path_with_query_string(self.scope),
490
+ self.scope["http_version"],
491
+ status_code,
492
+ )
493
+
494
+ # Write response status line and headers
495
+ content = [STATUS_LINE[status_code]]
496
+
497
+ for name, value in headers:
498
+ if HEADER_RE.search(name):
499
+ raise RuntimeError("Invalid HTTP header name.") # pragma: full coverage
500
+ if HEADER_VALUE_RE.search(value):
501
+ raise RuntimeError("Invalid HTTP header value.")
502
+
503
+ name = name.lower()
504
+ if name == b"content-length" and self.chunked_encoding is None:
505
+ self.expected_content_length = int(value.decode())
506
+ self.chunked_encoding = False
507
+ elif name == b"transfer-encoding" and value.lower() == b"chunked":
508
+ self.expected_content_length = 0
509
+ self.chunked_encoding = True
510
+ elif name == b"connection" and value.lower() == b"close":
511
+ self.keep_alive = False
512
+ content.extend([name, b": ", value, b"\r\n"])
513
+
514
+ if self.chunked_encoding is None and self.scope["method"] != "HEAD" and status_code not in (204, 304):
515
+ # Neither content-length nor transfer-encoding specified
516
+ self.chunked_encoding = True
517
+ content.append(b"transfer-encoding: chunked\r\n")
518
+
519
+ content.append(b"\r\n")
520
+ self.transport.write(b"".join(content))
521
+
522
+ elif not self.response_complete:
523
+ # Sending response body
524
+ if message["type"] != "http.response.body":
525
+ raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.")
526
+
527
+ body = message.get("body", b"")
528
+ more_body = message.get("more_body", False)
529
+
530
+ # Write response body
531
+ if self.scope["method"] == "HEAD":
532
+ self.expected_content_length = 0
533
+ elif self.chunked_encoding:
534
+ if body:
535
+ content = [b"%x\r\n" % len(body), body, b"\r\n"]
536
+ else:
537
+ content = []
538
+ if not more_body:
539
+ content.append(b"0\r\n\r\n")
540
+ self.transport.write(b"".join(content))
541
+ else:
542
+ num_bytes = len(body)
543
+ if num_bytes > self.expected_content_length:
544
+ raise RuntimeError("Response content longer than Content-Length")
545
+ else:
546
+ self.expected_content_length -= num_bytes
547
+ self.transport.write(body)
548
+
549
+ # Handle response completion
550
+ if not more_body:
551
+ if self.expected_content_length != 0:
552
+ raise RuntimeError("Response content shorter than Content-Length")
553
+ self.response_complete = True
554
+ self.message_event.set()
555
+ if not self.keep_alive:
556
+ self.transport.close()
557
+ self.on_response()
558
+
559
+ else:
560
+ # Response already sent
561
+ raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.")
562
+
563
+ async def receive(self) -> ASGIReceiveEvent:
564
+ if self.waiting_for_100_continue and not self.transport.is_closing():
565
+ self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
566
+ self.waiting_for_100_continue = False
567
+
568
+ if not self.disconnected and not self.response_complete:
569
+ self.flow.resume_reading()
570
+ await self.message_event.wait()
571
+ self.message_event.clear()
572
+
573
+ if self.disconnected or self.response_complete:
574
+ return {"type": "http.disconnect"}
575
+ message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body}
576
+ self.body = bytearray()
577
+ return message
.venv/Lib/site-packages/uvicorn/protocols/utils.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import socket
5
+ import urllib.parse
6
+
7
+ from uvicorn._types import WWWScope
8
+
9
+
10
+ class ClientDisconnected(OSError): ...
11
+
12
+
13
+ def get_remote_addr(transport: asyncio.Transport) -> tuple[str, int] | None:
14
+ socket_info: socket.socket | None = transport.get_extra_info("socket")
15
+ if socket_info is not None:
16
+ try:
17
+ info = socket_info.getpeername()
18
+ return (str(info[0]), int(info[1])) if isinstance(info, tuple) else None
19
+ except OSError: # pragma: no cover
20
+ # This case appears to inconsistently occur with uvloop
21
+ # bound to a unix domain socket.
22
+ return None
23
+
24
+ info = transport.get_extra_info("peername")
25
+ if info is not None and isinstance(info, list | tuple) and len(info) == 2:
26
+ return (str(info[0]), int(info[1]))
27
+ return None
28
+
29
+
30
+ def get_local_addr(transport: asyncio.Transport) -> tuple[str, int | None] | None:
31
+ socket_info: socket.socket | None = transport.get_extra_info("socket")
32
+ if socket_info is not None:
33
+ info = socket_info.getsockname()
34
+ if isinstance(info, tuple):
35
+ return (str(info[0]), int(info[1]))
36
+ if isinstance(info, str):
37
+ return (info, None)
38
+ return None
39
+ info = transport.get_extra_info("sockname")
40
+ if info is not None and isinstance(info, list | tuple) and len(info) == 2:
41
+ return (str(info[0]), int(info[1]))
42
+ if isinstance(info, str):
43
+ return (info, None)
44
+ return None
45
+
46
+
47
+ def is_ssl(transport: asyncio.Transport) -> bool:
48
+ return bool(transport.get_extra_info("sslcontext"))
49
+
50
+
51
+ def get_client_addr(scope: WWWScope) -> str:
52
+ client = scope.get("client")
53
+ if not client:
54
+ return ""
55
+ return "%s:%d" % client
56
+
57
+
58
+ def get_path_with_query_string(scope: WWWScope) -> str:
59
+ path_with_query_string = urllib.parse.quote(scope["path"])
60
+ if scope["query_string"]:
61
+ path_with_query_string = "{}?{}".format(path_with_query_string, scope["query_string"].decode("ascii"))
62
+ return path_with_query_string
.venv/Lib/site-packages/uvicorn/protocols/websockets/__init__.py ADDED
File without changes
.venv/Lib/site-packages/uvicorn/protocols/websockets/auto.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Callable
5
+
6
+ AutoWebSocketsProtocol: Callable[..., asyncio.Protocol] | None
7
+ try:
8
+ import websockets # noqa
9
+ except ImportError: # pragma: no cover
10
+ try:
11
+ import wsproto # noqa
12
+ except ImportError:
13
+ AutoWebSocketsProtocol = None
14
+ else:
15
+ from uvicorn.protocols.websockets.wsproto_impl import WSProtocol
16
+
17
+ AutoWebSocketsProtocol = WSProtocol
18
+ else:
19
+ from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol
20
+
21
+ AutoWebSocketsProtocol = WebSocketsSansIOProtocol
.venv/Lib/site-packages/uvicorn/protocols/websockets/websockets_impl.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import http
5
+ import logging
6
+ import warnings
7
+ from collections.abc import Sequence
8
+ from typing import Any, Literal, cast
9
+ from urllib.parse import unquote
10
+
11
+ import websockets
12
+ import websockets.legacy.handshake
13
+ from websockets.datastructures import Headers
14
+ from websockets.exceptions import ConnectionClosed
15
+ from websockets.extensions.base import ServerExtensionFactory
16
+ from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory
17
+ from websockets.legacy.server import HTTPResponse, WebSocketServerProtocol
18
+ from websockets.typing import Subprotocol
19
+
20
+ from uvicorn._types import (
21
+ ASGI3Application,
22
+ ASGISendEvent,
23
+ WebSocketConnectEvent,
24
+ WebSocketDisconnectEvent,
25
+ WebSocketReceiveEvent,
26
+ WebSocketScope,
27
+ )
28
+ from uvicorn.config import Config, UvicornDeprecationWarning
29
+ from uvicorn.logging import TRACE_LOG_LEVEL
30
+ from uvicorn.protocols.utils import (
31
+ ClientDisconnected,
32
+ get_client_addr,
33
+ get_local_addr,
34
+ get_path_with_query_string,
35
+ get_remote_addr,
36
+ is_ssl,
37
+ )
38
+ from uvicorn.server import ServerState
39
+
40
+ warnings.warn(
41
+ "The `websockets` implementation is deprecated, and `--ws websockets` will point at the "
42
+ "`websockets-sansio` implementation in a future release. "
43
+ "Use `--ws websockets-sansio` or `--ws auto` to switch now.",
44
+ UvicornDeprecationWarning,
45
+ )
46
+
47
+
48
+ class Server:
49
+ closing = False
50
+
51
+ def register(self, ws: WebSocketServerProtocol) -> None:
52
+ pass
53
+
54
+ def unregister(self, ws: WebSocketServerProtocol) -> None:
55
+ pass
56
+
57
+ def is_serving(self) -> bool:
58
+ return not self.closing
59
+
60
+
61
+ class WebSocketProtocol(WebSocketServerProtocol):
62
+ extra_headers: list[tuple[str, str]]
63
+ logger: logging.Logger | logging.LoggerAdapter[Any]
64
+
65
+ def __init__(
66
+ self,
67
+ config: Config,
68
+ server_state: ServerState,
69
+ app_state: dict[str, Any],
70
+ _loop: asyncio.AbstractEventLoop | None = None,
71
+ ):
72
+ if not config.loaded:
73
+ config.load()
74
+
75
+ self.config = config
76
+ self.app = cast(ASGI3Application, config.loaded_app)
77
+ self.loop = _loop or asyncio.get_event_loop()
78
+ self.root_path = config.root_path
79
+ self.asgi_version = config.asgi_version
80
+ self.app_state = app_state
81
+
82
+ # Shared server state
83
+ self.connections = server_state.connections
84
+ self.tasks = server_state.tasks
85
+
86
+ # Connection state
87
+ self.transport: asyncio.Transport = None # type: ignore[assignment]
88
+ self.server: tuple[str, int | None] | None = None
89
+ self.client: tuple[str, int] | None = None
90
+ self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment]
91
+
92
+ # Connection events
93
+ self.scope: WebSocketScope
94
+ self.handshake_started_event = asyncio.Event()
95
+ self.handshake_completed_event = asyncio.Event()
96
+ self.closed_event = asyncio.Event()
97
+ self.initial_response: HTTPResponse | None = None
98
+ self.connect_sent = False
99
+ self.lost_connection_before_handshake = False
100
+ self.accepted_subprotocol: Subprotocol | None = None
101
+
102
+ self.ws_server: Server = Server() # type: ignore[assignment]
103
+
104
+ extensions: list[ServerExtensionFactory] = []
105
+ if self.config.ws_per_message_deflate:
106
+ extensions.append(ServerPerMessageDeflateFactory())
107
+
108
+ super().__init__(
109
+ ws_handler=self.ws_handler,
110
+ ws_server=self.ws_server, # type: ignore[arg-type]
111
+ max_size=self.config.ws_max_size,
112
+ max_queue=self.config.ws_max_queue,
113
+ ping_interval=self.config.ws_ping_interval,
114
+ ping_timeout=self.config.ws_ping_timeout,
115
+ extensions=extensions,
116
+ logger=logging.getLogger("uvicorn.error"),
117
+ )
118
+ self.server_header = None
119
+ self.extra_headers = [
120
+ (name.decode("latin-1"), value.decode("latin-1")) for name, value in server_state.default_headers
121
+ ]
122
+
123
+ def connection_made( # type: ignore[override]
124
+ self, transport: asyncio.Transport
125
+ ) -> None:
126
+ self.connections.add(self)
127
+ self.transport = transport
128
+ self.server = get_local_addr(transport)
129
+ self.client = get_remote_addr(transport)
130
+ self.scheme = "wss" if is_ssl(transport) else "ws"
131
+
132
+ if self.logger.isEnabledFor(TRACE_LOG_LEVEL):
133
+ prefix = "%s:%d - " % self.client if self.client else ""
134
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix)
135
+
136
+ super().connection_made(transport)
137
+
138
+ def connection_lost(self, exc: Exception | None) -> None:
139
+ self.connections.remove(self)
140
+
141
+ if self.logger.isEnabledFor(TRACE_LOG_LEVEL):
142
+ prefix = "%s:%d - " % self.client if self.client else ""
143
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix)
144
+
145
+ self.lost_connection_before_handshake = not self.handshake_completed_event.is_set()
146
+ self.handshake_completed_event.set()
147
+ super().connection_lost(exc)
148
+ if exc is None:
149
+ self.transport.close()
150
+
151
+ def shutdown(self) -> None:
152
+ self.ws_server.closing = True
153
+ if self.handshake_completed_event.is_set():
154
+ self.fail_connection(1012)
155
+ else:
156
+ self.send_500_response()
157
+ self.transport.close()
158
+
159
+ def on_task_complete(self, task: asyncio.Task[None]) -> None:
160
+ self.tasks.discard(task)
161
+
162
+ async def process_request(self, path: str, request_headers: Headers) -> HTTPResponse | None:
163
+ """
164
+ This hook is called to determine if the websocket should return
165
+ an HTTP response and close.
166
+
167
+ Our behavior here is to start the ASGI application, and then wait
168
+ for either `accept` or `close` in order to determine if we should
169
+ close the connection.
170
+ """
171
+ path_portion, _, query_string = path.partition("?")
172
+
173
+ websockets.legacy.handshake.check_request(request_headers)
174
+
175
+ subprotocols: list[str] = []
176
+ for header in request_headers.get_all("Sec-WebSocket-Protocol"):
177
+ subprotocols.extend([token.strip() for token in header.split(",")])
178
+
179
+ asgi_headers = [
180
+ (name.encode("ascii"), value.encode("ascii", errors="surrogateescape"))
181
+ for name, value in request_headers.raw_items()
182
+ ]
183
+ path = unquote(path_portion)
184
+ full_path = self.root_path + path
185
+ full_raw_path = self.root_path.encode("ascii") + path_portion.encode("ascii")
186
+
187
+ self.scope = {
188
+ "type": "websocket",
189
+ "asgi": {"version": self.asgi_version, "spec_version": "2.4"},
190
+ "http_version": "1.1",
191
+ "scheme": self.scheme,
192
+ "server": self.server,
193
+ "client": self.client,
194
+ "root_path": self.root_path,
195
+ "path": full_path,
196
+ "raw_path": full_raw_path,
197
+ "query_string": query_string.encode("ascii"),
198
+ "headers": asgi_headers,
199
+ "subprotocols": subprotocols,
200
+ "state": self.app_state.copy(),
201
+ "extensions": {"websocket.http.response": {}},
202
+ }
203
+ task = self.loop.create_task(self.run_asgi())
204
+ task.add_done_callback(self.on_task_complete)
205
+ self.tasks.add(task)
206
+ await self.handshake_started_event.wait()
207
+ return self.initial_response
208
+
209
+ def process_subprotocol(
210
+ self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None
211
+ ) -> Subprotocol | None:
212
+ """
213
+ We override the standard 'process_subprotocol' behavior here so that
214
+ we return whatever subprotocol is sent in the 'accept' message.
215
+ """
216
+ return self.accepted_subprotocol
217
+
218
+ def send_500_response(self) -> None:
219
+ msg = b"Internal Server Error"
220
+ content = [
221
+ b"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=utf-8\r\n",
222
+ b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n",
223
+ b"connection: close\r\n",
224
+ b"\r\n",
225
+ msg,
226
+ ]
227
+ self.transport.write(b"".join(content))
228
+ # Allow handler task to terminate cleanly, as websockets doesn't cancel it by
229
+ # itself (see https://github.com/Kludex/uvicorn/issues/920)
230
+ self.handshake_started_event.set()
231
+
232
+ async def ws_handler(self, protocol: WebSocketServerProtocol, path: str) -> Any: # type: ignore[override]
233
+ """
234
+ This is the main handler function for the 'websockets' implementation
235
+ to call into. We just wait for close then return, and instead allow
236
+ 'send' and 'receive' events to drive the flow.
237
+ """
238
+ self.handshake_completed_event.set()
239
+ await self.wait_closed()
240
+
241
+ async def run_asgi(self) -> None:
242
+ """
243
+ Wrapper around the ASGI callable, handling exceptions and unexpected
244
+ termination states.
245
+ """
246
+ try:
247
+ result = await self.app(self.scope, self.asgi_receive, self.asgi_send) # type: ignore[func-returns-value]
248
+ except ClientDisconnected: # pragma: full coverage
249
+ self.closed_event.set()
250
+ except BaseException:
251
+ self.closed_event.set()
252
+ self.logger.exception("Exception in ASGI application\n")
253
+ if not self.handshake_started_event.is_set():
254
+ self.send_500_response()
255
+ else:
256
+ await self.handshake_completed_event.wait()
257
+ else:
258
+ self.closed_event.set()
259
+ if not self.handshake_started_event.is_set():
260
+ self.logger.error("ASGI callable returned without sending handshake.")
261
+ self.send_500_response()
262
+ elif result is not None:
263
+ self.logger.error("ASGI callable should return None, but returned '%s'.", result)
264
+ await self.handshake_completed_event.wait()
265
+ self.transport.close()
266
+
267
+ async def asgi_send(self, message: ASGISendEvent) -> None:
268
+ if not self.handshake_started_event.is_set():
269
+ if message["type"] == "websocket.accept":
270
+ self.logger.info(
271
+ '%s - "WebSocket %s" [accepted]',
272
+ get_client_addr(self.scope),
273
+ get_path_with_query_string(self.scope),
274
+ )
275
+ self.initial_response = None
276
+ self.accepted_subprotocol = cast(Subprotocol | None, message.get("subprotocol"))
277
+ if "headers" in message:
278
+ self.extra_headers.extend(
279
+ # ASGI spec requires bytes
280
+ # But for compatibility we need to convert it to strings
281
+ (name.decode("latin-1"), value.decode("latin-1"))
282
+ for name, value in message["headers"]
283
+ )
284
+ self.handshake_started_event.set()
285
+
286
+ elif message["type"] == "websocket.close":
287
+ self.logger.info(
288
+ '%s - "WebSocket %s" 403',
289
+ get_client_addr(self.scope),
290
+ get_path_with_query_string(self.scope),
291
+ )
292
+ self.initial_response = (http.HTTPStatus.FORBIDDEN, [], b"")
293
+ self.handshake_started_event.set()
294
+ self.closed_event.set()
295
+
296
+ elif message["type"] == "websocket.http.response.start":
297
+ self.logger.info(
298
+ '%s - "WebSocket %s" %d',
299
+ get_client_addr(self.scope),
300
+ get_path_with_query_string(self.scope),
301
+ message["status"],
302
+ )
303
+ # websockets requires the status to be an enum. look it up.
304
+ status = http.HTTPStatus(message["status"])
305
+ headers = [
306
+ (name.decode("latin-1"), value.decode("latin-1")) for name, value in message.get("headers", [])
307
+ ]
308
+ self.initial_response = (status, headers, b"")
309
+ self.handshake_started_event.set()
310
+
311
+ else:
312
+ raise RuntimeError(
313
+ "Expected ASGI message 'websocket.accept', 'websocket.close', "
314
+ f"or 'websocket.http.response.start' but got '{message['type']}'."
315
+ )
316
+
317
+ elif not self.closed_event.is_set() and self.initial_response is None:
318
+ await self.handshake_completed_event.wait()
319
+
320
+ try:
321
+ if message["type"] == "websocket.send":
322
+ bytes_data = message.get("bytes")
323
+ text_data = message.get("text")
324
+ data = text_data if bytes_data is None else bytes_data
325
+ await self.send(data) # type: ignore[arg-type]
326
+
327
+ elif message["type"] == "websocket.close":
328
+ code = message.get("code", 1000)
329
+ reason = message.get("reason", "") or ""
330
+ await self.close(code, reason)
331
+ self.closed_event.set()
332
+
333
+ else:
334
+ raise RuntimeError(
335
+ f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'."
336
+ )
337
+ except ConnectionClosed as exc:
338
+ raise ClientDisconnected from exc
339
+
340
+ elif self.initial_response is not None:
341
+ if message["type"] == "websocket.http.response.body":
342
+ body = self.initial_response[2] + message["body"]
343
+ self.initial_response = self.initial_response[:2] + (body,)
344
+ if not message.get("more_body", False):
345
+ self.closed_event.set()
346
+ else:
347
+ raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.")
348
+
349
+ else:
350
+ raise RuntimeError(
351
+ f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close' "
352
+ "or response already completed."
353
+ )
354
+
355
+ async def asgi_receive(self) -> WebSocketDisconnectEvent | WebSocketConnectEvent | WebSocketReceiveEvent:
356
+ if not self.connect_sent:
357
+ self.connect_sent = True
358
+ return {"type": "websocket.connect"}
359
+
360
+ await self.handshake_completed_event.wait()
361
+
362
+ if self.lost_connection_before_handshake:
363
+ # If the handshake failed or the app closed before handshake completion,
364
+ # use 1006 Abnormal Closure.
365
+ return {"type": "websocket.disconnect", "code": 1006}
366
+
367
+ if self.closed_event.is_set():
368
+ return {"type": "websocket.disconnect", "code": 1005}
369
+
370
+ try:
371
+ data = await self.recv()
372
+ except ConnectionClosed:
373
+ self.closed_event.set()
374
+ if self.ws_server.closing:
375
+ return {"type": "websocket.disconnect", "code": 1012}
376
+ return {"type": "websocket.disconnect", "code": self.close_code or 1005, "reason": self.close_reason}
377
+
378
+ if isinstance(data, str):
379
+ return {"type": "websocket.receive", "text": data}
380
+ return {"type": "websocket.receive", "bytes": data}
.venv/Lib/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import random
6
+ import struct
7
+ import sys
8
+ from asyncio import TimerHandle
9
+ from asyncio.transports import BaseTransport, Transport
10
+ from http import HTTPStatus
11
+ from typing import Any, Literal, cast
12
+ from urllib.parse import unquote
13
+
14
+ from websockets.exceptions import InvalidState
15
+ from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory
16
+ from websockets.frames import Frame, Opcode
17
+ from websockets.http11 import Request
18
+ from websockets.server import ServerProtocol
19
+
20
+ from uvicorn._types import (
21
+ ASGIReceiveEvent,
22
+ ASGISendEvent,
23
+ WebSocketScope,
24
+ )
25
+ from uvicorn.config import Config
26
+ from uvicorn.logging import TRACE_LOG_LEVEL
27
+ from uvicorn.protocols.utils import (
28
+ ClientDisconnected,
29
+ get_client_addr,
30
+ get_local_addr,
31
+ get_path_with_query_string,
32
+ get_remote_addr,
33
+ is_ssl,
34
+ )
35
+ from uvicorn.server import ServerState
36
+
37
+ if sys.version_info >= (3, 11): # pragma: no cover
38
+ from typing import assert_never
39
+ else: # pragma: no cover
40
+ from typing_extensions import assert_never
41
+
42
+
43
+ class WebSocketsSansIOProtocol(asyncio.Protocol):
44
+ def __init__(
45
+ self,
46
+ config: Config,
47
+ server_state: ServerState,
48
+ app_state: dict[str, Any],
49
+ _loop: asyncio.AbstractEventLoop | None = None,
50
+ ) -> None:
51
+ if not config.loaded:
52
+ config.load() # pragma: no cover
53
+
54
+ self.config = config
55
+ self.app = config.loaded_app
56
+ self.loop = _loop or asyncio.get_event_loop()
57
+ self.logger = logging.getLogger("uvicorn.error")
58
+ self.root_path = config.root_path
59
+ self.asgi_version = config.asgi_version
60
+ self.app_state = app_state
61
+
62
+ # Shared server state
63
+ self.connections = server_state.connections
64
+ self.tasks = server_state.tasks
65
+ self.default_headers = server_state.default_headers
66
+
67
+ # Connection state
68
+ self.transport: asyncio.Transport = None # type: ignore[assignment]
69
+ self.server: tuple[str, int | None] | None = None
70
+ self.client: tuple[str, int] | None = None
71
+ self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment]
72
+
73
+ # WebSocket state
74
+ self.queue: asyncio.Queue[ASGIReceiveEvent] = asyncio.Queue()
75
+ self.handshake_initiated = False
76
+ self.handshake_complete = False
77
+ self.close_sent = False
78
+ self.initial_response: tuple[int, list[tuple[str, str]], bytes] | None = None
79
+
80
+ extensions = []
81
+ if self.config.ws_per_message_deflate:
82
+ extensions = [
83
+ ServerPerMessageDeflateFactory(
84
+ server_max_window_bits=12,
85
+ client_max_window_bits=12,
86
+ compress_settings={"memLevel": 5},
87
+ )
88
+ ]
89
+ self.conn = ServerProtocol(
90
+ extensions=extensions,
91
+ max_size=self.config.ws_max_size,
92
+ logger=logging.getLogger("uvicorn.error"),
93
+ )
94
+
95
+ self.read_paused = False
96
+ self.writable = asyncio.Event()
97
+ self.writable.set()
98
+
99
+ # Keepalive state
100
+ self.ping_interval = config.ws_ping_interval
101
+ self.ping_timeout = config.ws_ping_timeout
102
+ self.ping_timer: TimerHandle | None = None
103
+ self.pong_timer: TimerHandle | None = None
104
+ self.pending_ping_payload: bytes | None = None
105
+ self.ping_sent_at: float = 0.0
106
+ self.last_ping_rtt: float = 0.0
107
+
108
+ # Incoming message state
109
+ self.frames: list[bytes] = []
110
+ self.curr_msg_data_type: Literal["text", "bytes"] = "bytes"
111
+
112
+ def connection_made(self, transport: BaseTransport) -> None:
113
+ """Called when a connection is made."""
114
+ transport = cast(Transport, transport)
115
+ self.connections.add(self)
116
+ self.transport = transport
117
+ self.server = get_local_addr(transport)
118
+ self.client = get_remote_addr(transport)
119
+ self.scheme = "wss" if is_ssl(transport) else "ws"
120
+
121
+ if self.logger.level <= TRACE_LOG_LEVEL:
122
+ prefix = "%s:%d - " % self.client if self.client else ""
123
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix)
124
+
125
+ def connection_lost(self, exc: Exception | None) -> None:
126
+ self.stop_keepalive()
127
+ code = 1005 if self.handshake_complete else 1006
128
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code})
129
+ self.connections.remove(self)
130
+
131
+ if self.logger.level <= TRACE_LOG_LEVEL:
132
+ prefix = "%s:%d - " % self.client if self.client else ""
133
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix)
134
+
135
+ self.handshake_complete = True
136
+ if exc is None:
137
+ self.transport.close()
138
+
139
+ def eof_received(self) -> None:
140
+ pass
141
+
142
+ def shutdown(self) -> None:
143
+ self.stop_keepalive()
144
+ if self.handshake_complete:
145
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012})
146
+ self.conn.send_close(1012)
147
+ output = self.conn.data_to_send()
148
+ self.transport.write(b"".join(output))
149
+ else:
150
+ self.send_500_response()
151
+ self.transport.close()
152
+
153
+ def data_received(self, data: bytes) -> None:
154
+ self.conn.receive_data(data)
155
+ if self.conn.parser_exc is not None: # pragma: no cover
156
+ self.handle_parser_exception()
157
+ return
158
+ self.handle_events()
159
+
160
+ def handle_events(self) -> None:
161
+ for event in self.conn.events_received():
162
+ if isinstance(event, Request):
163
+ self.handle_connect(event)
164
+ if isinstance(event, Frame):
165
+ if event.opcode == Opcode.CONT:
166
+ self.handle_cont(event) # pragma: no cover
167
+ elif event.opcode == Opcode.TEXT:
168
+ self.handle_text(event)
169
+ elif event.opcode == Opcode.BINARY:
170
+ self.handle_bytes(event)
171
+ elif event.opcode == Opcode.PING:
172
+ self.handle_ping()
173
+ elif event.opcode == Opcode.PONG:
174
+ self.handle_pong(event)
175
+ elif event.opcode == Opcode.CLOSE:
176
+ self.handle_close(event)
177
+ else:
178
+ assert_never(event.opcode) # pragma: no cover
179
+
180
+ # Event handlers
181
+
182
+ def handle_connect(self, event: Request) -> None:
183
+ self.request = event
184
+ self.response = self.conn.accept(event)
185
+ self.handshake_initiated = True
186
+ if self.response.status_code != 101:
187
+ self.handshake_complete = True
188
+ self.close_sent = True
189
+ self.conn.send_response(self.response)
190
+ output = self.conn.data_to_send()
191
+ self.transport.write(b"".join(output))
192
+ self.transport.close()
193
+ return
194
+
195
+ headers = [
196
+ (key.encode("ascii"), value.encode("ascii", errors="surrogateescape"))
197
+ for key, value in event.headers.raw_items()
198
+ ]
199
+ raw_path, _, query_string = event.path.partition("?")
200
+ subprotocols: list[str] = []
201
+ for header in event.headers.get_all("Sec-WebSocket-Protocol"):
202
+ subprotocols.extend([token.strip() for token in header.split(",")])
203
+ self.scope: WebSocketScope = {
204
+ "type": "websocket",
205
+ "asgi": {"version": self.asgi_version, "spec_version": "2.4"},
206
+ "http_version": "1.1",
207
+ "scheme": self.scheme,
208
+ "server": self.server,
209
+ "client": self.client,
210
+ "root_path": self.root_path,
211
+ "path": self.root_path + unquote(raw_path),
212
+ "raw_path": self.root_path.encode("ascii") + raw_path.encode("ascii"),
213
+ "query_string": query_string.encode("ascii"),
214
+ "headers": headers,
215
+ "subprotocols": subprotocols,
216
+ "state": self.app_state.copy(),
217
+ "extensions": {"websocket.http.response": {}},
218
+ }
219
+ self.queue.put_nowait({"type": "websocket.connect"})
220
+ task = self.loop.create_task(self.run_asgi())
221
+ task.add_done_callback(self.on_task_complete)
222
+ self.tasks.add(task)
223
+
224
+ def handle_cont(self, event: Frame) -> None:
225
+ self.frames.append(event.data) # type: ignore[arg-type]
226
+ if event.fin:
227
+ self.send_receive_event_to_app()
228
+
229
+ def handle_text(self, event: Frame) -> None:
230
+ self.curr_msg_data_type = "text"
231
+ self.frames = [event.data] # type: ignore[list-item]
232
+ if event.fin:
233
+ self.send_receive_event_to_app()
234
+
235
+ def handle_bytes(self, event: Frame) -> None:
236
+ self.curr_msg_data_type = "bytes"
237
+ self.frames = [event.data] # type: ignore[list-item]
238
+ if event.fin:
239
+ self.send_receive_event_to_app()
240
+
241
+ def send_receive_event_to_app(self) -> None:
242
+ data = self.frames[0] if len(self.frames) == 1 else b"".join(self.frames)
243
+ self.frames = []
244
+ if self.curr_msg_data_type == "text":
245
+ try:
246
+ self.queue.put_nowait({"type": "websocket.receive", "text": data.decode()})
247
+ except UnicodeDecodeError: # pragma: no cover
248
+ self.logger.exception("Invalid UTF-8 sequence received from client.")
249
+ self.conn.send_close(1007)
250
+ self.handle_parser_exception()
251
+ return
252
+ else:
253
+ self.queue.put_nowait({"type": "websocket.receive", "bytes": data})
254
+ if not self.read_paused:
255
+ self.read_paused = True
256
+ self.transport.pause_reading()
257
+
258
+ def handle_ping(self) -> None:
259
+ output = self.conn.data_to_send()
260
+ self.transport.write(b"".join(output))
261
+
262
+ def handle_pong(self, event: Frame) -> None:
263
+ # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight
264
+ if self.pending_ping_payload is None or bytes(event.data) != self.pending_ping_payload:
265
+ return # pragma: no cover
266
+
267
+ self.last_ping_rtt = self.loop.time() - self.ping_sent_at
268
+ self.pending_ping_payload = None
269
+ # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is
270
+ # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is
271
+ # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here.
272
+ if self.pong_timer is not None:
273
+ self.pong_timer.cancel()
274
+ self.pong_timer = None
275
+ self.schedule_ping()
276
+
277
+ def start_keepalive(self) -> None:
278
+ if self.ping_interval is not None and self.ping_interval > 0:
279
+ self.schedule_ping()
280
+
281
+ def stop_keepalive(self) -> None:
282
+ if self.ping_timer is not None:
283
+ self.ping_timer.cancel()
284
+ self.ping_timer = None
285
+ if self.pong_timer is not None: # pragma: no cover
286
+ self.pong_timer.cancel()
287
+ self.pong_timer = None
288
+ self.pending_ping_payload = None
289
+
290
+ def schedule_ping(self) -> None:
291
+ assert self.ping_interval is not None
292
+ delay = max(0.0, self.ping_interval - self.last_ping_rtt)
293
+ self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping)
294
+
295
+ def send_keepalive_ping(self) -> None:
296
+ self.ping_timer = None
297
+ if self.close_sent or self.transport.is_closing(): # pragma: no cover
298
+ return
299
+ # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs.
300
+ # See https://github.com/python-websockets/websockets/blob/4d229bf9f583d593aa103287aee0a77c9fbc3a79/src/websockets/asyncio/connection.py#L624
301
+ self.pending_ping_payload = struct.pack("!I", random.getrandbits(32))
302
+ self.ping_sent_at = self.loop.time()
303
+ self.conn.send_ping(self.pending_ping_payload)
304
+ self.transport.write(b"".join(self.conn.data_to_send()))
305
+ if self.ping_timeout is not None:
306
+ self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout)
307
+ else: # pragma: no cover
308
+ self.schedule_ping()
309
+
310
+ def keepalive_timeout(self) -> None:
311
+ self.pong_timer = None
312
+ self.pending_ping_payload = None
313
+ if self.close_sent or self.transport.is_closing(): # pragma: no cover
314
+ return
315
+ if self.logger.level <= TRACE_LOG_LEVEL:
316
+ prefix = "%s:%d - " % self.client if self.client else ""
317
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix)
318
+ self.conn.fail(1011, "keepalive ping timeout")
319
+ self.transport.write(b"".join(self.conn.data_to_send()))
320
+ self.close_sent = True
321
+ self.transport.close()
322
+
323
+ def handle_close(self, event: Frame) -> None:
324
+ if not self.close_sent and not self.transport.is_closing():
325
+ assert self.conn.close_rcvd is not None
326
+ code = self.conn.close_rcvd.code
327
+ reason = self.conn.close_rcvd.reason
328
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason})
329
+
330
+ output = self.conn.data_to_send()
331
+ self.transport.write(b"".join(output))
332
+ self.transport.close()
333
+
334
+ def handle_parser_exception(self) -> None: # pragma: no cover
335
+ assert self.conn.close_sent is not None
336
+ code = self.conn.close_sent.code
337
+ reason = self.conn.close_sent.reason
338
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason})
339
+
340
+ output = self.conn.data_to_send()
341
+ self.transport.write(b"".join(output))
342
+ self.close_sent = True
343
+ self.transport.close()
344
+
345
+ def on_task_complete(self, task: asyncio.Task[None]) -> None:
346
+ self.tasks.discard(task)
347
+
348
+ async def run_asgi(self) -> None:
349
+ try:
350
+ result = await self.app(self.scope, self.receive, self.send)
351
+ except ClientDisconnected:
352
+ pass # pragma: full coverage
353
+ except BaseException:
354
+ self.logger.exception("Exception in ASGI application\n")
355
+ self.send_500_response()
356
+ else:
357
+ if not self.handshake_complete:
358
+ self.logger.error("ASGI callable returned without completing handshake.")
359
+ self.send_500_response()
360
+ elif result is not None:
361
+ self.logger.error("ASGI callable should return None, but returned '%s'.", result)
362
+ self.transport.close()
363
+
364
+ def send_500_response(self) -> None:
365
+ if self.initial_response or self.handshake_complete:
366
+ return
367
+ response = self.conn.reject(500, "Internal Server Error")
368
+ self.conn.send_response(response)
369
+ output = self.conn.data_to_send()
370
+ self.transport.write(b"".join(output))
371
+
372
+ async def send(self, message: ASGISendEvent) -> None:
373
+ await self.writable.wait()
374
+
375
+ if not self.handshake_complete and self.initial_response is None:
376
+ if message["type"] == "websocket.accept":
377
+ self.logger.info(
378
+ '%s - "WebSocket %s" [accepted]',
379
+ get_client_addr(self.scope),
380
+ get_path_with_query_string(self.scope),
381
+ )
382
+ headers = [
383
+ (name.decode("latin-1").lower(), value.decode("latin-1"))
384
+ for name, value in (self.default_headers + list(message.get("headers", [])))
385
+ ]
386
+ accepted_subprotocol = message.get("subprotocol")
387
+ if accepted_subprotocol:
388
+ headers.append(("Sec-WebSocket-Protocol", accepted_subprotocol))
389
+ self.response.headers.update(headers)
390
+
391
+ if not self.transport.is_closing():
392
+ self.handshake_complete = True
393
+ self.conn.send_response(self.response)
394
+ output = self.conn.data_to_send()
395
+ self.transport.write(b"".join(output))
396
+ self.start_keepalive()
397
+
398
+ elif message["type"] == "websocket.close":
399
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006})
400
+ self.logger.info(
401
+ '%s - "WebSocket %s" 403',
402
+ get_client_addr(self.scope),
403
+ get_path_with_query_string(self.scope),
404
+ )
405
+ response = self.conn.reject(HTTPStatus.FORBIDDEN, "")
406
+ self.conn.send_response(response)
407
+ output = self.conn.data_to_send()
408
+ self.close_sent = True
409
+ self.handshake_complete = True
410
+ self.transport.write(b"".join(output))
411
+ self.transport.close()
412
+ elif message["type"] == "websocket.http.response.start" and self.initial_response is None:
413
+ if not (100 <= message["status"] < 600):
414
+ raise RuntimeError("Invalid HTTP status code '%d' in response." % message["status"])
415
+ self.logger.info(
416
+ '%s - "WebSocket %s" %d',
417
+ get_client_addr(self.scope),
418
+ get_path_with_query_string(self.scope),
419
+ message["status"],
420
+ )
421
+ headers = [
422
+ (name.decode("latin-1"), value.decode("latin-1"))
423
+ for name, value in list(message.get("headers", []))
424
+ ]
425
+ self.initial_response = (message["status"], headers, b"")
426
+ else:
427
+ raise RuntimeError(
428
+ "Expected ASGI message 'websocket.accept', 'websocket.close' "
429
+ f"or 'websocket.http.response.start' but got '{message['type']}'."
430
+ )
431
+
432
+ elif not self.close_sent and self.initial_response is None:
433
+ try:
434
+ if message["type"] == "websocket.send":
435
+ bytes_data = message.get("bytes")
436
+ text_data = message.get("text")
437
+ if bytes_data is not None:
438
+ self.conn.send_binary(bytes_data)
439
+ elif text_data is not None:
440
+ self.conn.send_text(text_data.encode())
441
+ output = self.conn.data_to_send()
442
+ self.transport.write(b"".join(output))
443
+
444
+ elif message["type"] == "websocket.close":
445
+ if not self.transport.is_closing():
446
+ code = message.get("code", 1000)
447
+ reason = message.get("reason", "") or ""
448
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason})
449
+ self.conn.send_close(code, reason)
450
+ output = self.conn.data_to_send()
451
+ self.transport.write(b"".join(output))
452
+ self.close_sent = True
453
+ self.transport.close()
454
+ else:
455
+ raise RuntimeError(
456
+ f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'."
457
+ )
458
+ except InvalidState:
459
+ raise ClientDisconnected()
460
+ elif self.initial_response is not None:
461
+ if message["type"] == "websocket.http.response.body":
462
+ body = self.initial_response[2] + message["body"]
463
+ self.initial_response = self.initial_response[:2] + (body,)
464
+ if not message.get("more_body", False):
465
+ response = self.conn.reject(self.initial_response[0], body.decode())
466
+ response.headers.update(self.initial_response[1])
467
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006})
468
+ self.conn.send_response(response)
469
+ output = self.conn.data_to_send()
470
+ self.close_sent = True
471
+ self.transport.write(b"".join(output))
472
+ self.transport.close()
473
+ else: # pragma: no cover
474
+ raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.")
475
+
476
+ else:
477
+ raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.")
478
+
479
+ async def receive(self) -> ASGIReceiveEvent:
480
+ message = await self.queue.get()
481
+ if self.read_paused and self.queue.empty():
482
+ self.read_paused = False
483
+ self.transport.resume_reading()
484
+ return message
.venv/Lib/site-packages/uvicorn/protocols/websockets/wsproto_impl.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import random
6
+ import struct
7
+ from asyncio import TimerHandle
8
+ from io import BytesIO, StringIO
9
+ from typing import Any, Literal, cast
10
+ from urllib.parse import unquote
11
+
12
+ import wsproto
13
+ from wsproto import ConnectionType, events
14
+ from wsproto.connection import ConnectionState
15
+ from wsproto.extensions import Extension, PerMessageDeflate
16
+ from wsproto.utilities import LocalProtocolError, RemoteProtocolError
17
+
18
+ from uvicorn._types import (
19
+ ASGI3Application,
20
+ ASGISendEvent,
21
+ WebSocketEvent,
22
+ WebSocketReceiveEvent,
23
+ WebSocketScope,
24
+ )
25
+ from uvicorn.config import Config
26
+ from uvicorn.logging import TRACE_LOG_LEVEL
27
+ from uvicorn.protocols.utils import (
28
+ ClientDisconnected,
29
+ get_client_addr,
30
+ get_local_addr,
31
+ get_path_with_query_string,
32
+ get_remote_addr,
33
+ is_ssl,
34
+ )
35
+ from uvicorn.server import ServerState
36
+
37
+
38
+ class FrameTooLargeError(Exception):
39
+ """Raised when accumulated websocket message bytes exceed `ws_max_size`."""
40
+
41
+
42
+ class WebsocketBuffer:
43
+ def __init__(self, max_length: int) -> None:
44
+ self.value: BytesIO | StringIO | None = None
45
+ self.length = 0
46
+ self.max_length = max_length
47
+
48
+ def extend(self, event: events.TextMessage | events.BytesMessage) -> None:
49
+ if self.value is None:
50
+ self.value = StringIO() if isinstance(event, events.TextMessage) else BytesIO()
51
+ self.value.write(event.data) # type: ignore[arg-type]
52
+ # `ws_max_size` is a byte budget, so count UTF-8 bytes for text.
53
+ self.length += len(event.data.encode()) if isinstance(event, events.TextMessage) else len(event.data)
54
+ if self.length > self.max_length:
55
+ raise FrameTooLargeError
56
+
57
+ def clear(self) -> None:
58
+ self.value = None
59
+ self.length = 0
60
+
61
+ def to_message(self) -> WebSocketReceiveEvent:
62
+ if isinstance(self.value, StringIO):
63
+ return {"type": "websocket.receive", "text": self.value.getvalue()}
64
+ assert isinstance(self.value, BytesIO)
65
+ return {"type": "websocket.receive", "bytes": self.value.getvalue()}
66
+
67
+
68
+ class WSProtocol(asyncio.Protocol):
69
+ def __init__(
70
+ self,
71
+ config: Config,
72
+ server_state: ServerState,
73
+ app_state: dict[str, Any],
74
+ _loop: asyncio.AbstractEventLoop | None = None,
75
+ ) -> None:
76
+ if not config.loaded:
77
+ config.load() # pragma: full coverage
78
+
79
+ self.config = config
80
+ self.app = cast(ASGI3Application, config.loaded_app)
81
+ self.loop = _loop or asyncio.get_event_loop()
82
+ self.logger = logging.getLogger("uvicorn.error")
83
+ self.root_path = config.root_path
84
+ self.asgi_version = config.asgi_version
85
+ self.app_state = app_state
86
+
87
+ # Shared server state
88
+ self.connections = server_state.connections
89
+ self.tasks = server_state.tasks
90
+ self.default_headers = server_state.default_headers
91
+
92
+ # Connection state
93
+ self.transport: asyncio.Transport = None # type: ignore[assignment]
94
+ self.server: tuple[str, int | None] | None = None
95
+ self.client: tuple[str, int] | None = None
96
+ self.scheme: Literal["wss", "ws"] = None # type: ignore[assignment]
97
+
98
+ # WebSocket state
99
+ self.queue: asyncio.Queue[WebSocketEvent] = asyncio.Queue()
100
+ self.handshake_complete = False
101
+ self.close_sent = False
102
+
103
+ # Rejection state
104
+ self.response_started = False
105
+
106
+ self.conn = wsproto.WSConnection(connection_type=ConnectionType.SERVER)
107
+
108
+ self.read_paused = False
109
+ self.writable = asyncio.Event()
110
+ self.writable.set()
111
+
112
+ # Keepalive state
113
+ self.ping_interval = config.ws_ping_interval
114
+ self.ping_timeout = config.ws_ping_timeout
115
+ self.ping_timer: TimerHandle | None = None
116
+ self.pong_timer: TimerHandle | None = None
117
+ self.pending_ping_payload: bytes | None = None
118
+ self.ping_sent_at: float = 0.0
119
+ self.last_ping_rtt: float = 0.0
120
+
121
+ # Buffer
122
+ self.buffer = WebsocketBuffer(self.config.ws_max_size)
123
+
124
+ # Protocol interface
125
+
126
+ def connection_made(self, transport: asyncio.Transport) -> None: # type: ignore[override]
127
+ self.connections.add(self)
128
+ self.transport = transport
129
+ self.server = get_local_addr(transport)
130
+ self.client = get_remote_addr(transport)
131
+ self.scheme = "wss" if is_ssl(transport) else "ws"
132
+
133
+ if self.logger.level <= TRACE_LOG_LEVEL:
134
+ prefix = "%s:%d - " % self.client if self.client else ""
135
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection made", prefix)
136
+
137
+ def connection_lost(self, exc: Exception | None) -> None:
138
+ self.stop_keepalive()
139
+ code = 1005 if self.handshake_complete else 1006
140
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code})
141
+ self.connections.remove(self)
142
+
143
+ if self.logger.level <= TRACE_LOG_LEVEL:
144
+ prefix = "%s:%d - " % self.client if self.client else ""
145
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix)
146
+
147
+ self.handshake_complete = True
148
+ if exc is None:
149
+ self.transport.close()
150
+
151
+ def eof_received(self) -> None:
152
+ pass
153
+
154
+ def data_received(self, data: bytes) -> None:
155
+ try:
156
+ self.conn.receive_data(data)
157
+ except RemoteProtocolError as err:
158
+ # TODO: Remove `type: ignore` when wsproto fixes the type annotation.
159
+ self.transport.write(self.conn.send(err.event_hint)) # type: ignore[arg-type] # noqa: E501
160
+ self.transport.close()
161
+ except LocalProtocolError:
162
+ # Data received after the close handshake completed, e.g. a pong the client
163
+ # sent while the server's close was in flight.
164
+ self.transport.close()
165
+ else:
166
+ self.handle_events()
167
+
168
+ def handle_events(self) -> None:
169
+ for event in self.conn.events():
170
+ if self.close_sent:
171
+ return
172
+ if isinstance(event, events.Request):
173
+ self.handle_connect(event)
174
+ elif isinstance(event, (events.TextMessage, events.BytesMessage)):
175
+ self.handle_message(event)
176
+ elif isinstance(event, events.CloseConnection):
177
+ self.handle_close(event)
178
+ elif isinstance(event, events.Ping):
179
+ self.handle_ping(event)
180
+ elif isinstance(event, events.Pong):
181
+ self.handle_pong(event)
182
+
183
+ def pause_writing(self) -> None:
184
+ """
185
+ Called by the transport when the write buffer exceeds the high water mark.
186
+ """
187
+ self.writable.clear() # pragma: full coverage
188
+
189
+ def resume_writing(self) -> None:
190
+ """
191
+ Called by the transport when the write buffer drops below the low water mark.
192
+ """
193
+ self.writable.set() # pragma: full coverage
194
+
195
+ def shutdown(self) -> None:
196
+ self.stop_keepalive()
197
+ if self.handshake_complete:
198
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012})
199
+ output = self.conn.send(wsproto.events.CloseConnection(code=1012))
200
+ self.transport.write(output)
201
+ else:
202
+ self.send_500_response()
203
+ self.transport.close()
204
+
205
+ def on_task_complete(self, task: asyncio.Task[None]) -> None:
206
+ self.tasks.discard(task)
207
+
208
+ # Event handlers
209
+
210
+ def handle_connect(self, event: events.Request) -> None:
211
+ headers = [(b"host", event.host.encode())]
212
+ headers += [(key.lower(), value) for key, value in event.extra_headers]
213
+ raw_path, _, query_string = event.target.partition("?")
214
+ path = unquote(raw_path)
215
+ full_path = self.root_path + path
216
+ full_raw_path = self.root_path.encode("ascii") + raw_path.encode("ascii")
217
+ self.scope: WebSocketScope = {
218
+ "type": "websocket",
219
+ "asgi": {"version": self.asgi_version, "spec_version": "2.4"},
220
+ "http_version": "1.1",
221
+ "scheme": self.scheme,
222
+ "server": self.server,
223
+ "client": self.client,
224
+ "root_path": self.root_path,
225
+ "path": full_path,
226
+ "raw_path": full_raw_path,
227
+ "query_string": query_string.encode("ascii"),
228
+ "headers": headers,
229
+ "subprotocols": event.subprotocols,
230
+ "state": self.app_state.copy(),
231
+ "extensions": {"websocket.http.response": {}},
232
+ }
233
+ self.queue.put_nowait({"type": "websocket.connect"})
234
+ task = self.loop.create_task(self.run_asgi())
235
+ task.add_done_callback(self.on_task_complete)
236
+ self.tasks.add(task)
237
+
238
+ def handle_message(self, event: events.TextMessage | events.BytesMessage) -> None:
239
+ try:
240
+ self.buffer.extend(event)
241
+ except FrameTooLargeError:
242
+ self.close_sent = True
243
+ reason = f"Message exceeds the maximum size ({self.config.ws_max_size} bytes)"
244
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1009, "reason": reason})
245
+ if not self.transport.is_closing():
246
+ self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1009, reason=reason)))
247
+ self.transport.close()
248
+ return
249
+ if event.message_finished:
250
+ self.queue.put_nowait(self.buffer.to_message())
251
+ self.buffer.clear()
252
+ if not self.read_paused:
253
+ self.read_paused = True
254
+ self.transport.pause_reading()
255
+
256
+ def handle_close(self, event: events.CloseConnection) -> None:
257
+ if self.conn.state == ConnectionState.REMOTE_CLOSING:
258
+ self.transport.write(self.conn.send(event.response()))
259
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": event.code, "reason": event.reason})
260
+ self.transport.close()
261
+
262
+ def handle_ping(self, event: events.Ping) -> None:
263
+ self.transport.write(self.conn.send(event.response()))
264
+
265
+ def handle_pong(self, event: events.Pong) -> None:
266
+ # Ignore unsolicited pongs and stale pongs whose payload doesn't match the ping currently in flight.
267
+ if self.pending_ping_payload is None or bytes(event.payload) != self.pending_ping_payload:
268
+ return # pragma: no cover
269
+
270
+ self.last_ping_rtt = self.loop.time() - self.ping_sent_at
271
+ self.pending_ping_payload = None
272
+ # The peer answered in time; cancel the pong deadline and chain the next ping. This `schedule_ping()` call is
273
+ # what keeps the keepalive loop running when ping_timeout is set. When ping_timeout is None the next ping is
274
+ # already scheduled by `send_keepalive_ping`, so we must not schedule a duplicate here.
275
+ if self.pong_timer is not None:
276
+ self.pong_timer.cancel()
277
+ self.pong_timer = None
278
+ self.schedule_ping()
279
+
280
+ def start_keepalive(self) -> None:
281
+ if self.ping_interval is not None and self.ping_interval > 0:
282
+ self.schedule_ping()
283
+
284
+ def stop_keepalive(self) -> None:
285
+ if self.ping_timer is not None:
286
+ self.ping_timer.cancel()
287
+ self.ping_timer = None
288
+ if self.pong_timer is not None: # pragma: no cover
289
+ self.pong_timer.cancel()
290
+ self.pong_timer = None
291
+ self.pending_ping_payload = None
292
+
293
+ def schedule_ping(self) -> None:
294
+ assert self.ping_interval is not None
295
+ delay = max(0.0, self.ping_interval - self.last_ping_rtt)
296
+ self.ping_timer = self.loop.call_later(delay, self.send_keepalive_ping)
297
+
298
+ def send_keepalive_ping(self) -> None:
299
+ self.ping_timer = None
300
+ if self.close_sent or self.transport.is_closing(): # pragma: no cover
301
+ return
302
+ # Random 4-byte payload identifies this ping; `handle_pong` uses it to ignore stale or unsolicited pongs.
303
+ self.pending_ping_payload = struct.pack("!I", random.getrandbits(32))
304
+ self.ping_sent_at = self.loop.time()
305
+ self.transport.write(self.conn.send(wsproto.events.Ping(payload=self.pending_ping_payload)))
306
+ if self.ping_timeout is not None:
307
+ self.pong_timer = self.loop.call_later(self.ping_timeout, self.keepalive_timeout)
308
+ else: # pragma: no cover
309
+ self.schedule_ping()
310
+
311
+ def keepalive_timeout(self) -> None:
312
+ self.pong_timer = None
313
+ self.pending_ping_payload = None
314
+ if self.close_sent or self.transport.is_closing(): # pragma: no cover
315
+ return
316
+ if self.logger.level <= TRACE_LOG_LEVEL:
317
+ prefix = "%s:%d - " % self.client if self.client else ""
318
+ self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket keepalive ping timeout", prefix)
319
+ reason = "keepalive ping timeout"
320
+ self.transport.write(self.conn.send(wsproto.events.CloseConnection(code=1011, reason=reason)))
321
+ self.close_sent = True
322
+ self.transport.close()
323
+
324
+ def send_500_response(self) -> None:
325
+ if self.response_started or self.handshake_complete:
326
+ return # we cannot send responses anymore
327
+ headers: list[tuple[bytes, bytes]] = [
328
+ (b"content-type", b"text/plain; charset=utf-8"),
329
+ (b"connection", b"close"),
330
+ (b"content-length", b"21"),
331
+ ]
332
+ output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True))
333
+ output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error"))
334
+ self.transport.write(output)
335
+
336
+ async def run_asgi(self) -> None:
337
+ try:
338
+ result = await self.app(self.scope, self.receive, self.send) # type: ignore[func-returns-value]
339
+ except ClientDisconnected:
340
+ pass # pragma: full coverage
341
+ except BaseException:
342
+ self.logger.exception("Exception in ASGI application\n")
343
+ self.send_500_response()
344
+ else:
345
+ if not self.handshake_complete:
346
+ self.logger.error("ASGI callable returned without completing handshake.")
347
+ self.send_500_response()
348
+ elif result is not None:
349
+ self.logger.error("ASGI callable should return None, but returned '%s'.", result)
350
+ self.transport.close()
351
+
352
+ async def send(self, message: ASGISendEvent) -> None:
353
+ await self.writable.wait()
354
+
355
+ if not self.handshake_complete:
356
+ if message["type"] == "websocket.accept":
357
+ self.logger.info(
358
+ '%s - "WebSocket %s" [accepted]',
359
+ get_client_addr(self.scope),
360
+ get_path_with_query_string(self.scope),
361
+ )
362
+ subprotocol = message.get("subprotocol")
363
+ extra_headers = self.default_headers + list(message.get("headers", []))
364
+ extensions: list[Extension] = []
365
+ if self.config.ws_per_message_deflate:
366
+ extensions.append(PerMessageDeflate())
367
+ if not self.transport.is_closing():
368
+ self.handshake_complete = True
369
+ output = self.conn.send(
370
+ wsproto.events.AcceptConnection(
371
+ subprotocol=subprotocol,
372
+ extensions=extensions,
373
+ extra_headers=extra_headers,
374
+ )
375
+ )
376
+ self.transport.write(output)
377
+ self.start_keepalive()
378
+
379
+ elif message["type"] == "websocket.close":
380
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006})
381
+ self.logger.info(
382
+ '%s - "WebSocket %s" 403',
383
+ get_client_addr(self.scope),
384
+ get_path_with_query_string(self.scope),
385
+ )
386
+ self.handshake_complete = True
387
+ self.close_sent = True
388
+ event = events.RejectConnection(status_code=403, headers=[])
389
+ output = self.conn.send(event)
390
+ self.transport.write(output)
391
+ self.transport.close()
392
+
393
+ elif message["type"] == "websocket.http.response.start":
394
+ # ensure status code is in the valid range
395
+ if not (100 <= message["status"] < 600):
396
+ msg = "Invalid HTTP status code '%d' in response."
397
+ raise RuntimeError(msg % message["status"])
398
+ self.logger.info(
399
+ '%s - "WebSocket %s" %d',
400
+ get_client_addr(self.scope),
401
+ get_path_with_query_string(self.scope),
402
+ message["status"],
403
+ )
404
+ self.handshake_complete = True
405
+ event = events.RejectConnection(
406
+ status_code=message["status"],
407
+ headers=list(message["headers"]),
408
+ has_body=True,
409
+ )
410
+ output = self.conn.send(event)
411
+ self.transport.write(output)
412
+ self.response_started = True
413
+
414
+ else:
415
+ raise RuntimeError(
416
+ "Expected ASGI message 'websocket.accept', 'websocket.close' "
417
+ f"or 'websocket.http.response.start' but got '{message['type']}'."
418
+ )
419
+
420
+ elif not self.close_sent and not self.response_started:
421
+ try:
422
+ if message["type"] == "websocket.send":
423
+ bytes_data = message.get("bytes")
424
+ text_data = message.get("text")
425
+ data = text_data if bytes_data is None else bytes_data
426
+ output = self.conn.send(wsproto.events.Message(data=data)) # type: ignore
427
+ if not self.transport.is_closing():
428
+ self.transport.write(output)
429
+
430
+ elif message["type"] == "websocket.close":
431
+ self.close_sent = True
432
+ code = message.get("code", 1000)
433
+ reason = message.get("reason", "") or ""
434
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": code, "reason": reason})
435
+ output = self.conn.send(wsproto.events.CloseConnection(code=code, reason=reason))
436
+ if not self.transport.is_closing():
437
+ self.transport.write(output)
438
+ self.transport.close()
439
+
440
+ else:
441
+ raise RuntimeError(
442
+ f"Expected ASGI message 'websocket.send' or 'websocket.close', but got '{message['type']}'."
443
+ )
444
+ except LocalProtocolError as exc:
445
+ raise ClientDisconnected from exc
446
+ elif self.response_started:
447
+ if message["type"] == "websocket.http.response.body":
448
+ body_finished = not message.get("more_body", False)
449
+ reject_data = events.RejectData(data=message["body"], body_finished=body_finished)
450
+ output = self.conn.send(reject_data)
451
+ self.transport.write(output)
452
+
453
+ if body_finished:
454
+ self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006})
455
+ self.close_sent = True
456
+ self.transport.close()
457
+
458
+ else:
459
+ raise RuntimeError(f"Expected ASGI message 'websocket.http.response.body' but got '{message['type']}'.")
460
+
461
+ else:
462
+ raise RuntimeError(f"Unexpected ASGI message '{message['type']}', after sending 'websocket.close'.")
463
+
464
+ async def receive(self) -> WebSocketEvent:
465
+ message = await self.queue.get()
466
+ if self.read_paused and self.queue.empty():
467
+ self.read_paused = False
468
+ self.transport.resume_reading()
469
+ return message
.venv/Lib/site-packages/uvicorn/py.typed ADDED
@@ -0,0 +1 @@
 
 
1
+
.venv/Lib/site-packages/uvicorn/server.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import functools
6
+ import logging
7
+ import os
8
+ import platform
9
+ import random
10
+ import signal
11
+ import socket
12
+ import sys
13
+ import threading
14
+ import time
15
+ from collections.abc import Generator, Sequence
16
+ from email.utils import formatdate
17
+ from types import FrameType
18
+ from typing import TYPE_CHECKING, TypeAlias
19
+
20
+ from uvicorn._ansi import style
21
+ from uvicorn._compat import asyncio_run
22
+ from uvicorn.config import STARTUP_FAILURE, Config
23
+
24
+ if TYPE_CHECKING:
25
+ from uvicorn.protocols.http.h11_impl import H11Protocol
26
+ from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
27
+ from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol
28
+ from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol
29
+ from uvicorn.protocols.websockets.wsproto_impl import WSProtocol
30
+
31
+ Protocols: TypeAlias = H11Protocol | HttpToolsProtocol | WSProtocol | WebSocketProtocol | WebSocketsSansIOProtocol
32
+
33
+ HANDLED_SIGNALS = (
34
+ signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
35
+ signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
36
+ )
37
+ if sys.platform == "win32": # pragma: py-not-win32
38
+ HANDLED_SIGNALS += (signal.SIGBREAK,) # Windows signal 21. Sent by Ctrl+Break.
39
+
40
+ logger = logging.getLogger("uvicorn.error")
41
+
42
+
43
+ class ServerState:
44
+ """
45
+ Shared servers state that is available between all protocol instances.
46
+ """
47
+
48
+ def __init__(self) -> None:
49
+ self.total_requests = 0
50
+ self.connections: set[Protocols] = set()
51
+ self.tasks: set[asyncio.Task[None]] = set()
52
+ self.default_headers: list[tuple[bytes, bytes]] = []
53
+
54
+
55
+ class Server:
56
+ def __init__(self, config: Config) -> None:
57
+ self.config = config
58
+ self.server_state = ServerState()
59
+
60
+ self.started = False
61
+ self.should_exit = False
62
+ self.force_exit = False
63
+ self.last_notified = 0.0
64
+
65
+ self._captured_signals: list[int] = []
66
+
67
+ @functools.cached_property
68
+ def limit_max_requests(self) -> int | None:
69
+ if self.config.limit_max_requests is None:
70
+ return None
71
+ return self.config.limit_max_requests + random.randint(0, self.config.limit_max_requests_jitter)
72
+
73
+ def run(self, sockets: list[socket.socket] | None = None) -> None:
74
+ return asyncio_run(self.serve(sockets=sockets), loop_factory=self.config.get_loop_factory())
75
+
76
+ async def serve(self, sockets: list[socket.socket] | None = None) -> None:
77
+ with self.capture_signals():
78
+ await self._serve(sockets)
79
+
80
+ async def _serve(self, sockets: list[socket.socket] | None = None) -> None:
81
+ process_id = os.getpid()
82
+
83
+ config = self.config
84
+ if not config.loaded:
85
+ config.load()
86
+
87
+ self.lifespan = config.lifespan_class(config)
88
+
89
+ message = "Started server process [%d]"
90
+ color_message = "Started server process [" + style("%d", fg="cyan") + "]"
91
+ logger.info(message, process_id, extra={"color_message": color_message})
92
+
93
+ await self.startup(sockets=sockets)
94
+ if not self.should_exit:
95
+ await self.main_loop()
96
+ if self.started:
97
+ await self.shutdown(sockets=sockets)
98
+
99
+ message = "Finished server process [%d]"
100
+ color_message = "Finished server process [" + style("%d", fg="cyan") + "]"
101
+ logger.info(message, process_id, extra={"color_message": color_message})
102
+
103
+ async def startup(self, sockets: list[socket.socket] | None = None) -> None:
104
+ await self.lifespan.startup()
105
+ if self.lifespan.should_exit:
106
+ sys.exit(STARTUP_FAILURE)
107
+
108
+ config = self.config
109
+
110
+ def create_protocol(
111
+ _loop: asyncio.AbstractEventLoop | None = None,
112
+ ) -> asyncio.Protocol:
113
+ return config.http_protocol_class( # type: ignore[call-arg]
114
+ config=config,
115
+ server_state=self.server_state,
116
+ app_state=self.lifespan.state,
117
+ _loop=_loop,
118
+ )
119
+
120
+ loop = asyncio.get_running_loop()
121
+
122
+ listeners: Sequence[socket.SocketType]
123
+ if sockets is not None: # pragma: full coverage
124
+ # Explicitly passed a list of open sockets.
125
+ # We use this when the server is run from a Gunicorn worker.
126
+
127
+ def _share_socket(
128
+ sock: socket.SocketType,
129
+ ) -> socket.SocketType: # pragma py-not-win32
130
+ # Windows requires the socket be explicitly shared across
131
+ # multiple workers (processes).
132
+ from socket import fromshare # type: ignore[attr-defined]
133
+
134
+ sock_data = sock.share(os.getpid()) # type: ignore[attr-defined]
135
+ return fromshare(sock_data)
136
+
137
+ self.servers: list[asyncio.base_events.Server] = []
138
+ for sock in sockets:
139
+ is_windows = platform.system() == "Windows"
140
+ if config.workers > 1 and is_windows: # pragma: py-not-win32
141
+ sock = _share_socket(sock) # type: ignore[assignment]
142
+ server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)
143
+ self.servers.append(server)
144
+ listeners = sockets
145
+
146
+ elif config.fd is not None: # pragma: py-win32
147
+ # Use an existing socket, from a file descriptor.
148
+ sock = socket.fromfd(config.fd, socket.AF_UNIX, socket.SOCK_STREAM)
149
+ server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)
150
+ assert server.sockets is not None # mypy
151
+ listeners = server.sockets
152
+ self.servers = [server]
153
+
154
+ elif config.uds is not None: # pragma: py-win32
155
+ # Create a socket using UNIX domain socket.
156
+ uds_perms = 0o666
157
+ if os.path.exists(config.uds):
158
+ uds_perms = os.stat(config.uds).st_mode # pragma: full coverage
159
+ server = await loop.create_unix_server(
160
+ create_protocol, path=config.uds, ssl=config.ssl, backlog=config.backlog
161
+ )
162
+ os.chmod(config.uds, uds_perms)
163
+ assert server.sockets is not None # mypy
164
+ listeners = server.sockets
165
+ self.servers = [server]
166
+
167
+ else:
168
+ # Standard case. Create a socket from a host/port pair.
169
+ try:
170
+ server = await loop.create_server(
171
+ create_protocol,
172
+ host=config.host,
173
+ port=config.port,
174
+ ssl=config.ssl,
175
+ backlog=config.backlog,
176
+ )
177
+ except OSError as exc:
178
+ logger.error(exc)
179
+ await self.lifespan.shutdown()
180
+ sys.exit(STARTUP_FAILURE)
181
+
182
+ assert server.sockets is not None
183
+ listeners = server.sockets
184
+ self.servers = [server]
185
+
186
+ if sockets is None:
187
+ self._log_started_message(listeners)
188
+ else:
189
+ # We're most likely running multiple workers, so a message has already been
190
+ # logged by `config.bind_socket()`.
191
+ pass # pragma: full coverage
192
+
193
+ self.started = True
194
+
195
+ def _log_started_message(self, listeners: Sequence[socket.SocketType]) -> None:
196
+ config = self.config
197
+
198
+ if config.fd is not None: # pragma: py-win32
199
+ sock = listeners[0]
200
+ logger.info(
201
+ "Uvicorn running on socket %s (Press CTRL+C to quit)",
202
+ sock.getsockname(),
203
+ )
204
+
205
+ elif config.uds is not None: # pragma: py-win32
206
+ logger.info("Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds)
207
+
208
+ else:
209
+ addr_format = "%s://%s:%d"
210
+ host = "0.0.0.0" if config.host is None else config.host
211
+ if ":" in host:
212
+ # It's an IPv6 address.
213
+ addr_format = "%s://[%s]:%d"
214
+
215
+ port = config.port
216
+ if port == 0:
217
+ port = listeners[0].getsockname()[1]
218
+
219
+ protocol_name = "https" if config.ssl else "http"
220
+ message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
221
+ color_message = "Uvicorn running on " + style(addr_format, bold=True) + " (Press CTRL+C to quit)"
222
+ logger.info(
223
+ message,
224
+ protocol_name,
225
+ host,
226
+ port,
227
+ extra={"color_message": color_message},
228
+ )
229
+
230
+ async def main_loop(self) -> None:
231
+ counter = 0
232
+ should_exit = await self.on_tick(counter)
233
+ while not should_exit:
234
+ counter += 1
235
+ counter = counter % 864000
236
+ await asyncio.sleep(0.1)
237
+ should_exit = await self.on_tick(counter)
238
+
239
+ async def on_tick(self, counter: int) -> bool:
240
+ # Update the default headers, once per second.
241
+ if counter % 10 == 0:
242
+ current_time = time.time()
243
+ current_date = formatdate(current_time, usegmt=True).encode()
244
+
245
+ if self.config.date_header:
246
+ date_header = [(b"date", current_date)]
247
+ else:
248
+ date_header = []
249
+
250
+ self.server_state.default_headers = date_header + self.config.encoded_headers
251
+
252
+ # Callback to `callback_notify` once every `timeout_notify` seconds.
253
+ if self.config.callback_notify is not None:
254
+ if current_time - self.last_notified > self.config.timeout_notify: # pragma: full coverage
255
+ self.last_notified = current_time
256
+ await self.config.callback_notify()
257
+
258
+ # Determine if we should exit.
259
+ if self.should_exit:
260
+ return True
261
+
262
+ max_requests = self.limit_max_requests
263
+ if max_requests is not None and self.server_state.total_requests >= max_requests:
264
+ logger.info("Maximum request limit of %d exceeded. Terminating process.", max_requests)
265
+ return True
266
+
267
+ return False
268
+
269
+ async def shutdown(self, sockets: list[socket.socket] | None = None) -> None:
270
+ logger.info("Shutting down")
271
+
272
+ # Stop accepting new connections.
273
+ for server in self.servers:
274
+ server.close()
275
+ for sock in sockets or []:
276
+ sock.close() # pragma: full coverage
277
+
278
+ # Request shutdown on all existing connections.
279
+ for connection in list(self.server_state.connections):
280
+ connection.shutdown()
281
+ await asyncio.sleep(0.1)
282
+
283
+ # When 3.10 is not supported anymore, use `async with asyncio.timeout(...):`.
284
+ try:
285
+ await asyncio.wait_for(
286
+ self._wait_tasks_to_complete(),
287
+ timeout=self.config.timeout_graceful_shutdown,
288
+ )
289
+ except asyncio.TimeoutError:
290
+ logger.error(
291
+ "Cancel %s running task(s), timeout graceful shutdown exceeded",
292
+ len(self.server_state.tasks),
293
+ )
294
+ for t in self.server_state.tasks:
295
+ t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
296
+
297
+ # Send the lifespan shutdown event, and wait for application shutdown.
298
+ if not self.force_exit:
299
+ await self.lifespan.shutdown()
300
+
301
+ async def _wait_tasks_to_complete(self) -> None:
302
+ # Wait for existing connections to finish sending responses.
303
+ if self.server_state.connections and not self.force_exit:
304
+ msg = "Waiting for connections to close. (CTRL+C to force quit)"
305
+ logger.info(msg)
306
+ while self.server_state.connections and not self.force_exit:
307
+ await asyncio.sleep(0.1)
308
+
309
+ # Wait for existing tasks to complete.
310
+ if self.server_state.tasks and not self.force_exit:
311
+ msg = "Waiting for background tasks to complete. (CTRL+C to force quit)"
312
+ logger.info(msg)
313
+ while self.server_state.tasks and not self.force_exit:
314
+ await asyncio.sleep(0.1)
315
+
316
+ for server in self.servers:
317
+ await server.wait_closed()
318
+
319
+ @contextlib.contextmanager
320
+ def capture_signals(self) -> Generator[None, None, None]:
321
+ # Signals can only be listened to from the main thread.
322
+ if threading.current_thread() is not threading.main_thread():
323
+ yield
324
+ return
325
+ # always use signal.signal, even if loop.add_signal_handler is available
326
+ # this allows to restore previous signal handlers later on
327
+ original_handlers = {sig: signal.signal(sig, self.handle_exit) for sig in HANDLED_SIGNALS}
328
+ try:
329
+ yield
330
+ finally:
331
+ for sig, handler in original_handlers.items():
332
+ signal.signal(sig, handler)
333
+ # If we did gracefully shut down due to a signal, try to
334
+ # trigger the expected behaviour now; multiple signals would be
335
+ # done LIFO, see https://stackoverflow.com/questions/48434964
336
+ for captured_signal in reversed(self._captured_signals):
337
+ signal.raise_signal(captured_signal)
338
+
339
+ def handle_exit(self, sig: int, frame: FrameType | None) -> None:
340
+ self._captured_signals.append(sig)
341
+ if self.should_exit and sig == signal.SIGINT:
342
+ self.force_exit = True # pragma: full coverage
343
+ else:
344
+ self.should_exit = True
.venv/Lib/site-packages/uvicorn/supervisors/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from uvicorn.supervisors.basereload import BaseReload
6
+ from uvicorn.supervisors.multiprocess import Multiprocess
7
+
8
+ if TYPE_CHECKING:
9
+ ChangeReload: type[BaseReload]
10
+ else:
11
+ try:
12
+ from uvicorn.supervisors.watchfilesreload import WatchFilesReload as ChangeReload
13
+ except ImportError: # pragma: no cover
14
+ from uvicorn.supervisors.statreload import StatReload as ChangeReload
15
+
16
+ __all__ = ["Multiprocess", "ChangeReload"]